diff options
Diffstat (limited to 'server/sonar-webserver-auth/src')
13 files changed, 320 insertions, 54 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 beacb12c6fd..7bc12dd1ef4 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 @@ -46,6 +46,11 @@ public class SafeModeUserSession extends AbstractUserSession { return false; } + @Override + protected boolean hasChildProjectsPermission(String permission, String applicationUuid) { + 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 9bf08d2ec8e..3d36461a0e5 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 @@ -19,7 +19,6 @@ */ package org.sonar.server.user; -import com.google.common.collect.ImmutableSet; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -40,7 +39,7 @@ import static org.apache.commons.lang.StringUtils.defaultString; import static org.sonar.server.user.UserSession.IdentityProvider.SONARQUBE; public abstract class AbstractUserSession implements UserSession { - private static final Set<String> PUBLIC_PERMISSIONS = ImmutableSet.of(UserRole.USER, UserRole.CODEVIEWER); + private static final Set<String> PUBLIC_PERMISSIONS = Set.of(UserRole.USER, UserRole.CODEVIEWER); private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges"; private static final String AUTHENTICATION_IS_REQUIRED_MESSAGE = "Authentication is required"; @@ -106,6 +105,14 @@ public abstract class AbstractUserSession implements UserSession { } @Override + public final boolean hasChildProjectsPermission(String permission, ComponentDto component) { + if (isRoot()) { + return true; + } + return hasChildProjectsPermission(permission, component.uuid()); + } + + @Override public final boolean hasComponentUuidPermission(String permission, String componentUuid) { if (isRoot()) { return true; @@ -120,6 +127,8 @@ public abstract class AbstractUserSession implements UserSession { protected abstract boolean hasProjectUuidPermission(String permission, String projectUuid); + protected abstract boolean hasChildProjectsPermission(String permission, String applicationUuid); + @Override public final List<ComponentDto> keepAuthorizedComponents(String permission, Collection<ComponentDto> components) { if (isRoot()) { @@ -188,7 +197,8 @@ public abstract class AbstractUserSession implements UserSession { return this; } - @Override public UserSession checkProjectPermission(String projectPermission, ProjectDto project) { + @Override + public UserSession checkProjectPermission(String projectPermission, ProjectDto project) { if (isRoot() || hasProjectUuidPermission(projectPermission, project.getUuid())) { return this; } @@ -197,6 +207,16 @@ public abstract class AbstractUserSession implements UserSession { } @Override + public UserSession checkChildProjectsPermission(String projectPermission, ComponentDto component) { + if (isRoot() || !component.qualifier().equals(Qualifiers.APP) || hasChildProjectsPermission(projectPermission, component.uuid())) { + return this; + } + + throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); + + } + + @Override public final UserSession checkComponentUuidPermission(String permission, String componentUuid) { if (!hasComponentUuidPermission(permission, componentUuid)) { throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); 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 c1427d07e23..ed2770c08c0 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 @@ -128,6 +128,11 @@ public final class DoPrivileged { } @Override + protected boolean hasChildProjectsPermission(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 c0909a7c686..0131f2ab492 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 @@ -29,16 +29,22 @@ import java.util.Optional; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTreeQuery; +import org.sonar.db.component.ComponentTreeQuery.Strategy; import org.sonar.db.permission.GlobalPermission; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; +import static java.util.Collections.singleton; import static java.util.Optional.of; import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toSet; import static org.apache.commons.lang.StringUtils.defaultIfEmpty; import static org.sonar.api.web.UserRole.PUBLIC_PERMISSIONS; @@ -158,6 +164,24 @@ public class ServerUserSession extends AbstractUserSession { if (permissionsByProjectUuid == null) { permissionsByProjectUuid = new HashMap<>(); } + return hasPermission(permission, projectUuid); + } + + @Override + protected boolean hasChildProjectsPermission(String permission, String applicationUuid) { + if (permissionsByProjectUuid == null) { + permissionsByProjectUuid = new HashMap<>(); + } + + Set<String> childProjectUuids = loadChildProjectUuids(applicationUuid); + + return childProjectUuids + .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); } @@ -181,6 +205,20 @@ public class ServerUserSession extends AbstractUserSession { } } + private Set<String> loadChildProjectUuids(String applicationUuid) { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.componentDao() + .selectDescendants(dbSession, ComponentTreeQuery.builder() + .setBaseUuid(applicationUuid) + .setQualifiers(singleton(Qualifiers.PROJECT)) + .setScopes(singleton(Scopes.FILE)) + .setStrategy(Strategy.CHILDREN).build()) + .stream() + .map(ComponentDto::getCopyComponentUuid) + .collect(toSet()); + } + } + 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 d9b06b04c3f..8102efd755f 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 @@ -145,6 +145,12 @@ public class ThreadLocalUserSession implements UserSession { } @Override + public UserSession checkChildProjectsPermission(String projectPermission, ComponentDto component) { + get().checkChildProjectsPermission(projectPermission, component); + return this; + } + + @Override public UserSession checkComponentUuidPermission(String permission, String componentUuid) { get().checkComponentUuidPermission(permission, componentUuid); return this; @@ -172,6 +178,11 @@ public class ThreadLocalUserSession implements UserSession { } @Override + public boolean hasChildProjectsPermission(String permission, ComponentDto component) { + return get().hasChildProjectsPermission(permission, component); + } + + @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 4bd33d3c0a5..fd6e8f71556 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 @@ -195,6 +195,8 @@ public interface UserSession { boolean hasProjectPermission(String permission, ProjectDto project); + boolean hasChildProjectsPermission(String permission, ComponentDto component); + /** * Using {@link #hasComponentPermission(String, ComponentDto)} is recommended * because it does not have to load project if the referenced component @@ -229,6 +231,12 @@ public interface UserSession { UserSession checkProjectPermission(String projectPermission, ProjectDto project); /** + * Ensures that {@link #hasChildProjectsPermission(String, ComponentDto)} is {@code true} + * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. + */ + UserSession checkChildProjectsPermission(String projectPermission, ComponentDto project); + + /** * Ensures that {@link #hasComponentUuidPermission(String, String)} is {@code true}, * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}. * 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 e98bdee7a9a..23ea9457b94 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 @@ -46,5 +46,6 @@ public class SafeModeUserSessionTest { assertThat(underTest.isSystemAdministrator()).isFalse(); assertThat(underTest.hasPermissionImpl(GlobalPermission.ADMINISTER)).isFalse(); assertThat(underTest.hasProjectUuidPermission(UserRole.USER, "foo")).isFalse(); + assertThat(underTest.hasChildProjectsPermission(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 3ce36ea935e..17dcd97dddb 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 @@ -26,6 +26,7 @@ import org.sonar.db.component.ComponentDto; import org.sonar.server.tester.MockUserSession; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.web.UserRole.USER; public class DoPrivilegedTest { @@ -50,6 +51,7 @@ public class DoPrivilegedTest { assertThat(catcher.userSession.hasComponentPermission("any permission", new ComponentDto())).isTrue(); assertThat(catcher.userSession.isSystemAdministrator()).isTrue(); assertThat(catcher.userSession.shouldResetPassword()).isFalse(); + assertThat(catcher.userSession.hasChildProjectsPermission(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 0d21463a0d5..ddebf91b08d 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 @@ -25,7 +25,6 @@ import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.System2; -import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; @@ -37,10 +36,15 @@ import static com.google.common.base.Preconditions.checkState; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.web.UserRole.ADMIN; +import static org.sonar.api.web.UserRole.CODEVIEWER; +import static org.sonar.api.web.UserRole.ISSUE_ADMIN; +import static org.sonar.api.web.UserRole.USER; import static org.sonar.core.permission.GlobalPermissions.PROVISIONING; 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.permission.GlobalPermission.ADMINISTER; import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; import static org.sonar.db.permission.GlobalPermission.SCAN; @@ -48,8 +52,8 @@ import static org.sonar.db.permission.GlobalPermission.SCAN; public class ServerUserSessionTest { @Rule - public DbTester db = DbTester.create(System2.INSTANCE); - private DbClient dbClient = db.getDbClient(); + public final DbTester db = DbTester.create(System2.INSTANCE); + private final DbClient dbClient = db.getDbClient(); @Test public void anonymous_is_not_logged_in_and_does_not_have_login() { @@ -157,9 +161,9 @@ public class ServerUserSessionTest { UserSession underTest = newUserSession(root); - assertThat(underTest.hasComponentUuidPermission(UserRole.USER, file.uuid())).isTrue(); - assertThat(underTest.hasComponentUuidPermission(UserRole.CODEVIEWER, file.uuid())).isTrue(); - assertThat(underTest.hasComponentUuidPermission(UserRole.ADMIN, file.uuid())).isTrue(); + assertThat(underTest.hasComponentUuidPermission(USER, file.uuid())).isTrue(); + assertThat(underTest.hasComponentUuidPermission(CODEVIEWER, file.uuid())).isTrue(); + assertThat(underTest.hasComponentUuidPermission(ADMIN, file.uuid())).isTrue(); assertThat(underTest.hasComponentUuidPermission("whatever", "who cares?")).isTrue(); } @@ -172,7 +176,7 @@ public class ServerUserSessionTest { UserSession underTest = newUserSession(root); - assertThat(underTest.checkComponentUuidPermission(UserRole.USER, file.uuid())).isSameAs(underTest); + assertThat(underTest.checkComponentUuidPermission(USER, file.uuid())).isSameAs(underTest); assertThat(underTest.checkComponentUuidPermission("whatever", "who cares?")).isSameAs(underTest); } @@ -180,10 +184,60 @@ public class ServerUserSessionTest { public void checkComponentUuidPermission_fails_with_FE_when_user_has_not_permission_for_specified_uuid_in_db() { UserDto user = db.users().insertUser(); ComponentDto project = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(user, UserRole.USER, project); + db.users().insertProjectPermissionOnUser(user, USER, project); UserSession session = newUserSession(user); - assertThatForbiddenExceptionIsThrown(() -> session.checkComponentUuidPermission(UserRole.USER, "another-uuid")); + assertThatForbiddenExceptionIsThrown(() -> session.checkComponentUuidPermission(USER, "another-uuid")); + } + + @Test + public void checkChildProjectsPermission_succeeds_if_user_is_root() { + UserDto root = db.users().insertUser(); + root = db.users().makeRoot(root); + ComponentDto project = db.components().insertPrivateProject(); + ComponentDto application = db.components().insertPrivateApplication(); + db.components().addApplicationProject(application, project); + + UserSession underTest = newUserSession(root); + + assertThat(underTest.checkChildProjectsPermission(USER, application)).isSameAs(underTest); + } + + @Test + public void checkChildProjectsPermission_succeeds_if_user_has_permissions_on_all_application_child_projects() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + db.users().insertProjectPermissionOnUser(user, USER, project); + ComponentDto application = db.components().insertPrivateApplication(); + db.components().addApplicationProject(application, project); + + UserSession underTest = newUserSession(user); + + assertThat(underTest.checkChildProjectsPermission(USER, application)).isSameAs(underTest); + } + + @Test + public void checkChildProjectsPermission_succeeds_if_component_is_not_an_application() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + + UserSession underTest = newUserSession(user); + + assertThat(underTest.checkChildProjectsPermission(USER, project)).isSameAs(underTest); + } + + @Test + public void checkChildProjectsPermission_fails_with_FE_when_user_has_not_permission_for_specified_uuid_in_db() { + UserDto user = db.users().insertUser(); + ComponentDto project = db.components().insertPrivateProject(); + ComponentDto application = db.components().insertPrivateApplication(); + db.components().addApplicationProject(application, project); + //add computed project + db.components().insertComponent(newProjectCopy(project, application)); + + UserSession underTest = newUserSession(user); + + assertThatForbiddenExceptionIsThrown(() -> underTest.checkChildProjectsPermission(USER, application)); } @Test @@ -215,7 +269,7 @@ public class ServerUserSessionTest { ComponentDto project = db.components().insertPrivateProject(); UserDto user = db.users().insertUser(); db.users().insertPermissionOnUser(user, PROVISION_PROJECTS); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, project); + db.users().insertProjectPermissionOnUser(user, ADMIN, project); UserSession session = newUserSession(user); assertThat(session.hasPermission(PROVISION_PROJECTS)).isTrue(); @@ -265,13 +319,90 @@ public class ServerUserSessionTest { } @Test + public void test_hasChildProjectsPermission_for_logged_in_user() { + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + UserDto user = db.users().insertUser(); + db.users().insertProjectPermissionOnUser(user, USER, project1); + + ComponentDto application = db.components().insertPrivateApplication(); + db.components().addApplicationProject(application, project1); + // add computed project + db.components().insertComponent(newProjectCopy(project1, application)); + + UserSession session = newUserSession(user); + assertThat(session.hasChildProjectsPermission(USER, application)).isTrue(); + + db.components().addApplicationProject(application, project2); + db.components().insertComponent(newProjectCopy(project2, application)); + + assertThat(session.hasChildProjectsPermission(USER, application)).isFalse(); + } + + @Test + public void test_hasChildProjectsPermission_for_anonymous_user() { + ComponentDto project = db.components().insertPrivateProject(); + db.users().insertPermissionOnAnyone(USER); + ComponentDto application = db.components().insertPrivateApplication(); + db.components().addApplicationProject(application, project); + // add computed project + db.components().insertComponent(newProjectCopy(project, application)); + + UserSession session = newAnonymousSession(); + assertThat(session.hasChildProjectsPermission(USER, application)).isFalse(); + } + + @Test + public void hasChildProjectsPermission_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 application = db.components().insertPrivateApplication(); + db.components().addApplicationProject(application, project); + // add computed project + db.components().insertComponent(newProjectCopy(project, application)); + + UserSession session = newUserSession(user); + + // feed the cache + assertThat(session.hasChildProjectsPermission(USER, application)).isTrue(); + + // change permissions without updating the cache + db.users().deletePermissionFromUser(project, user, USER); + assertThat(session.hasChildProjectsPermission(USER, application)).isTrue(); + + // cache is refreshed when user logs in again + session = newUserSession(user); + assertThat(session.hasChildProjectsPermission(USER, application)).isFalse(); + } + + @Test + public void hasChildProjectsPermission_keeps_cache_of_permissions_of_anonymous_user() { + db.users().insertPermissionOnAnyone(USER); + + ComponentDto project = db.components().insertPublicProject(); + ComponentDto application = db.components().insertPublicApplication(); + db.components().addApplicationProject(application, project); + + UserSession session = newAnonymousSession(); + + // feed the cache + assertThat(session.hasChildProjectsPermission(USER, application)).isTrue(); + + // change privacy of the project without updating the cache + db.getDbClient().componentDao().setPrivateForRootComponentUuidWithoutAudit(db.getSession(), project.uuid(), true); + assertThat(session.hasChildProjectsPermission(USER, application)).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(); ServerUserSession underTest = newAnonymousSession(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, publicProject)).isTrue(); } @Test @@ -281,8 +412,8 @@ public class ServerUserSessionTest { ServerUserSession underTest = newAnonymousSession(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, publicProject)).isTrue(); } @Test @@ -292,8 +423,8 @@ public class ServerUserSessionTest { ServerUserSession underTest = newAnonymousSession(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, publicProject)).isTrue(); } @Test @@ -303,8 +434,8 @@ public class ServerUserSessionTest { ServerUserSession underTest = newAnonymousSession(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, publicProject)).isTrue(); } @Test @@ -314,8 +445,8 @@ public class ServerUserSessionTest { ServerUserSession underTest = newUserSession(user); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, privateProject)).isFalse(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, privateProject)).isFalse(); } @Test @@ -326,8 +457,8 @@ public class ServerUserSessionTest { ServerUserSession underTest = newUserSession(user); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, privateProject)).isFalse(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, privateProject)).isFalse(); } @Test @@ -338,8 +469,8 @@ public class ServerUserSessionTest { ServerUserSession underTest = newUserSession(user); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, USER, privateProject)).isFalse(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, CODEVIEWER, privateProject)).isFalse(); } @Test @@ -411,35 +542,35 @@ public class ServerUserSessionTest { public void hasComponentPermissionByDtoOrUuid_keeps_cache_of_permissions_of_logged_in_user() { UserDto user = db.users().insertUser(); ComponentDto publicProject = db.components().insertPublicProject(); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, publicProject); + db.users().insertProjectPermissionOnUser(user, ADMIN, publicProject); UserSession underTest = newUserSession(user); // feed the cache - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, ADMIN, publicProject)).isTrue(); // change permissions without updating the cache - db.users().deletePermissionFromUser(publicProject, user, UserRole.ADMIN); - db.users().insertProjectPermissionOnUser(user, UserRole.ISSUE_ADMIN, publicProject); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ISSUE_ADMIN, publicProject)).isFalse(); + db.users().deletePermissionFromUser(publicProject, user, ADMIN); + db.users().insertProjectPermissionOnUser(user, ISSUE_ADMIN, publicProject); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, ADMIN, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, ISSUE_ADMIN, publicProject)).isFalse(); } @Test public void hasComponentPermissionByDtoOrUuid_keeps_cache_of_permissions_of_anonymous_user() { ComponentDto publicProject = db.components().insertPublicProject(); - db.users().insertProjectPermissionOnAnyone(UserRole.ADMIN, publicProject); + db.users().insertProjectPermissionOnAnyone(ADMIN, publicProject); UserSession underTest = newAnonymousSession(); // feed the cache - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, ADMIN, publicProject)).isTrue(); // change permissions without updating the cache - db.users().deleteProjectPermissionFromAnyone(publicProject, UserRole.ADMIN); - db.users().insertProjectPermissionOnAnyone(UserRole.ISSUE_ADMIN, publicProject); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ADMIN, publicProject)).isTrue(); - assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.ISSUE_ADMIN, publicProject)).isFalse(); + db.users().deleteProjectPermissionFromAnyone(publicProject, ADMIN); + db.users().insertProjectPermissionOnAnyone(ISSUE_ADMIN, publicProject); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, ADMIN, publicProject)).isTrue(); + assertThat(hasComponentPermissionByDtoOrUuid(underTest, ISSUE_ADMIN, publicProject)).isFalse(); } private boolean hasComponentPermissionByDtoOrUuid(UserSession underTest, String permission, ComponentDto component) { @@ -456,7 +587,7 @@ public class ServerUserSessionTest { UserSession underTest = newAnonymousSession(); - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); + assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); } @Test @@ -464,24 +595,24 @@ public class ServerUserSessionTest { UserDto user = db.users().insertUser(); ComponentDto publicProject = db.components().insertPublicProject(); ComponentDto privateProject = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, privateProject); + db.users().insertProjectPermissionOnUser(user, ADMIN, privateProject); UserSession underTest = newUserSession(user); - assertThat(underTest.keepAuthorizedComponents(UserRole.ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(privateProject); + assertThat(underTest.keepAuthorizedComponents(ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); + assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(privateProject); } @Test public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_anonymous() { ComponentDto publicProject = db.components().insertPublicProject(); ComponentDto privateProject = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnAnyone(UserRole.ISSUE_ADMIN, publicProject); + db.users().insertProjectPermissionOnAnyone(ISSUE_ADMIN, publicProject); UserSession underTest = newAnonymousSession(); - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); - assertThat(underTest.keepAuthorizedComponents(UserRole.ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(publicProject); + assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty(); + assertThat(underTest.keepAuthorizedComponents(ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(publicProject); } @Test @@ -493,7 +624,7 @@ public class ServerUserSessionTest { UserSession underTest = newUserSession(root); - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))) + assertThat(underTest.keepAuthorizedComponents(ADMIN, Arrays.asList(privateProject, publicProject))) .containsExactly(privateProject, publicProject); } @@ -501,12 +632,12 @@ public class ServerUserSessionTest { public void keepAuthorizedComponents_on_branches() { UserDto user = db.users().insertUser(); ComponentDto privateProject = db.components().insertPrivateProject(); - db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, privateProject); + db.users().insertProjectPermissionOnUser(user, ADMIN, privateProject); ComponentDto privateBranchProject = db.components().insertProjectBranch(privateProject); UserSession underTest = newUserSession(user); - assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, asList(privateProject, privateBranchProject))) + assertThat(underTest.keepAuthorizedComponents(ADMIN, asList(privateProject, privateBranchProject))) .containsExactlyInAnyOrder(privateProject, privateBranchProject); } 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 1183f5b0054..c9ccb3c984d 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 @@ -22,6 +22,7 @@ package org.sonar.server.user; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.sonar.db.component.ComponentDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.GroupTesting; import org.sonar.server.exceptions.UnauthorizedException; @@ -30,10 +31,11 @@ import org.sonar.server.tester.MockUserSession; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.web.UserRole.USER; public class ThreadLocalUserSessionTest { - private ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession(); + private final ThreadLocalUserSession threadLocalUserSession = new ThreadLocalUserSession(); @Before public void setUp() { @@ -65,6 +67,7 @@ public class ThreadLocalUserSessionTest { assertThat(threadLocalUserSession.isLoggedIn()).isTrue(); assertThat(threadLocalUserSession.shouldResetPassword()).isTrue(); assertThat(threadLocalUserSession.getGroups()).extracting(GroupDto::getUuid).containsOnly(group.getUuid()); + assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ComponentDto())).isFalse(); } @Test @@ -82,7 +85,7 @@ public class ThreadLocalUserSessionTest { @Test public void throw_UnauthorizedException_when_no_session() { - assertThatThrownBy(() -> threadLocalUserSession.get()) + assertThatThrownBy(threadLocalUserSession::get) .isInstanceOf(UnauthorizedException.class); } 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 b46b99ffe27..9e040fb3a7c 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 @@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.sonar.api.web.UserRole; import org.sonar.db.component.ComponentDto; import org.sonar.db.permission.GlobalPermission; @@ -40,10 +41,11 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> private static final Set<String> PUBLIC_PERMISSIONS = ImmutableSet.of(UserRole.USER, UserRole.CODEVIEWER); // FIXME to check with Simon private final Class<T> clazz; - private HashMultimap<String, String> projectUuidByPermission = HashMultimap.create(); + private final HashMultimap<String, String> projectUuidByPermission = HashMultimap.create(); private final Set<GlobalPermission> permissions = new HashSet<>(); - private Map<String, String> projectUuidByComponentUuid = new HashMap<>(); - private Set<String> projectPermissions = new HashSet<>(); + private final Map<String, String> projectUuidByComponentUuid = new HashMap<>(); + private final Map<String, Set<String>> applicationProjects = new HashMap<>(); + private final Set<String> projectPermissions = new HashSet<>(); private boolean systemAdministrator = false; private boolean resetPassword = false; @@ -93,6 +95,18 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> return clazz.cast(this); } + public T registerApplication(ProjectDto application, ProjectDto... appProjects) { + registerProjects(application); + registerProjects(appProjects); + + var appProjectsUuid = Arrays.stream(appProjects) + .map(ProjectDto::getUuid) + .collect(Collectors.toSet()); + this.applicationProjects.put(application.getUuid(), appProjectsUuid); + + return clazz.cast(this); + } + public T registerPortfolios(PortfolioDto... portfolios) { Arrays.stream(portfolios) .forEach(portfolio -> { @@ -156,6 +170,13 @@ public abstract class AbstractMockUserSession<T extends AbstractMockUserSession> return projectPermissions.contains(permission) && projectUuidByPermission.get(permission).contains(projectUuid); } + @Override + protected boolean hasChildProjectsPermission(String permission, String applicationUuid) { + return applicationProjects.containsKey(applicationUuid) && applicationProjects.get(applicationUuid) + .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 b0bdbce1cf2..4b33d0ff77c 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 @@ -193,6 +193,11 @@ public class UserSessionRule implements TestRule, UserSession { return this; } + public UserSessionRule registerApplication(ProjectDto application, ProjectDto... appProjects) { + ensureAbstractMockUserSession().registerApplication(application, appProjects); + return this; + } + public UserSessionRule addProjectPermission(String projectPermission, ComponentDto... components) { ensureAbstractMockUserSession().addProjectPermission(projectPermission, components); return this; @@ -252,6 +257,11 @@ public class UserSessionRule implements TestRule, UserSession { } @Override + public boolean hasChildProjectsPermission(String permission, ComponentDto component) { + return currentUserSession.hasChildProjectsPermission(permission, component); + } + + @Override public boolean hasComponentUuidPermission(String permission, String componentUuid) { return currentUserSession.hasComponentUuidPermission(permission, componentUuid); } @@ -355,6 +365,12 @@ public class UserSessionRule implements TestRule, UserSession { } @Override + public UserSession checkChildProjectsPermission(String projectPermission, ComponentDto component) { + currentUserSession.checkChildProjectsPermission(projectPermission, component); + return this; + } + + @Override public UserSession checkComponentUuidPermission(String permission, String componentUuid) { currentUserSession.checkComponentUuidPermission(permission, componentUuid); return this; 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 97d3fe28b01..cf3a2336dff 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 @@ -122,6 +122,11 @@ public class TestUserSessionFactory implements UserSessionFactory { } @Override + protected boolean hasChildProjectsPermission(String permission, String applicationUuid) { + throw notImplemented(); + } + + @Override public boolean isSystemAdministrator() { throw notImplemented(); } |