diff options
Diffstat (limited to 'server')
6 files changed, 346 insertions, 66 deletions
diff --git a/server/sonar-webserver-auth/src/it/java/org/sonar/server/user/TokenUserSessionIT.java b/server/sonar-webserver-auth/src/it/java/org/sonar/server/user/TokenUserSessionIT.java index 8c9591c5544..fe40850c56a 100644 --- a/server/sonar-webserver-auth/src/it/java/org/sonar/server/user/TokenUserSessionIT.java +++ b/server/sonar-webserver-auth/src/it/java/org/sonar/server/user/TokenUserSessionIT.java @@ -19,22 +19,36 @@ */ package org.sonar.server.user; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.List; +import java.util.Set; import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; 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; +import org.sonar.db.component.ProjectData; import org.sonar.db.permission.GlobalPermission; import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.api.web.UserRole.CODEVIEWER; import static org.sonar.api.web.UserRole.SCAN; +import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.user.TokenType.GLOBAL_ANALYSIS_TOKEN; import static org.sonar.db.user.TokenType.PROJECT_ANALYSIS_TOKEN; +import static org.sonar.db.user.TokenType.PROJECT_BADGE_TOKEN; import static org.sonar.db.user.TokenType.USER_TOKEN; +@RunWith(DataProviderRunner.class) public class TokenUserSessionIT { @Rule @@ -59,7 +73,7 @@ public class TokenUserSessionIT { } @Test - public void test_hasProjectsPermission_for_UserToken() { + public void hasProjectsPermission_for_UserToken() { ProjectDto project1 = db.components().insertPrivateProject().getProjectDto(); ProjectDto project2 = db.components().insertPrivateProject().getProjectDto(); @@ -74,7 +88,7 @@ public class TokenUserSessionIT { } @Test - public void test_hasProjectsPermission_for_ProjecAnalysisToken() { + public void hasProjectsPermission_for_ProjecAnalysisToken() { ProjectDto project1 = db.components().insertPrivateProject().getProjectDto(); ProjectDto project2 = db.components().insertPrivateProject().getProjectDto(); @@ -83,14 +97,14 @@ public class TokenUserSessionIT { db.users().insertProjectPermissionOnUser(user, SCAN, project1); db.users().insertProjectPermissionOnUser(user, SCAN, project2); - TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1); + TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user, project1); assertThat(userSession.hasEntityUuidPermission(SCAN, project1.getUuid())).isTrue(); assertThat(userSession.hasEntityUuidPermission(SCAN, project2.getUuid())).isFalse(); } @Test - public void test_hasProjectsPermission_for_ProjectAnalysisToken_with_global_permission() { + public void hasProjectsPermission_for_ProjectAnalysisToken_with_global_permission() { ProjectDto project1 = db.components().insertPrivateProject().getProjectDto(); ProjectDto project2 = db.components().insertPrivateProject().getProjectDto(); @@ -98,14 +112,14 @@ public class TokenUserSessionIT { db.users().insertGlobalPermissionOnUser(user, GlobalPermission.SCAN); - TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1); + TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user, project1); assertThat(userSession.hasEntityUuidPermission(SCAN, project1.getUuid())).isTrue(); assertThat(userSession.hasEntityUuidPermission(SCAN, project2.getUuid())).isFalse(); } @Test - public void test_hasGlobalPermission_for_UserToken() { + public void hasGlobalPermission_for_UserToken() { UserDto user = db.users().insertUser(); db.users().insertGlobalPermissionOnUser(user, GlobalPermission.SCAN); @@ -115,7 +129,7 @@ public class TokenUserSessionIT { } @Test - public void test_hasGlobalPermission_for_ProjecAnalysisToken() { + public void hasGlobalPermission_for_ProjecAnalysisToken() { ProjectDto project1 = db.components().insertPrivateProject().getProjectDto(); ProjectDto project2 = db.components().insertPrivateProject().getProjectDto(); @@ -126,13 +140,13 @@ public class TokenUserSessionIT { db.users().insertGlobalPermissionOnUser(user, GlobalPermission.SCAN); - TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1); + TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user, project1); assertThat(userSession.hasPermission(GlobalPermission.SCAN)).isFalse(); } @Test - public void test_hasGlobalPermission_for_GlobalAnalysisToken() { + public void hasGlobalPermission_for_GlobalAnalysisToken() { ProjectDto project1 = db.components().insertPrivateProject().getProjectDto(); UserDto user = db.users().insertUser(); @@ -146,7 +160,7 @@ public class TokenUserSessionIT { } @Test - public void test_hasProvisionProjectsGlobalPermission_for_GlobalAnalysisToken_returnsTrueIfUserIsGranted() { + public void hasProvisionProjectsGlobalPermission_for_GlobalAnalysisToken_returnsTrueIfUserIsGranted() { UserDto user = db.users().insertUser(); db.users().insertGlobalPermissionOnUser(user, GlobalPermission.SCAN); @@ -158,7 +172,7 @@ public class TokenUserSessionIT { } @Test - public void test_hasProvisionProjectsGlobalPermission_for_GlobalAnalysisToken_returnsFalseIfUserIsNotGranted() { + public void hasProvisionProjectsGlobalPermission_for_GlobalAnalysisToken_returnsFalseIfUserIsNotGranted() { UserDto user = db.users().insertUser(); db.users().insertGlobalPermissionOnUser(user, GlobalPermission.SCAN); @@ -169,7 +183,7 @@ public class TokenUserSessionIT { } @Test - public void test_hasAdministerGlobalPermission_for_GlobalAnalysisToken_returnsFalse() { + public void hasAdministerGlobalPermission_for_GlobalAnalysisToken_returnsFalse() { UserDto user = db.users().insertUser(); db.users().insertGlobalPermissionOnUser(user, GlobalPermission.ADMINISTER); @@ -179,6 +193,202 @@ public class TokenUserSessionIT { assertThat(userSession.hasPermission(GlobalPermission.ADMINISTER)).isFalse(); } + @Test + public void keepAuthorizedEntities_shouldFilterProjects_whenGlobalAnalysisToken() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + ProjectData privateProjectWithoutAccess = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, USER, privateProject.getProjectDto()); + + Set<ProjectDto> projectDto = Set.of(publicProject.getProjectDto(), privateProject.getProjectDto()); + List<ProjectDto> projectDtos = mockGlobalAnalysisTokenUserSession(user).keepAuthorizedEntities(USER, projectDto); + + assertThat(projectDtos).containsExactlyInAnyOrder(privateProject.getProjectDto(), publicProject.getProjectDto()) + .doesNotContain(privateProjectWithoutAccess.getProjectDto()); + } + + @Test + @UseDataProvider("validPermissions") + public void keepAuthorizedEntities_shouldFilterPrivateProjects_whenProjectAnalysisToken(String permission) { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + ProjectData privateProjectWithoutAccess = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, permission, privateProject.getProjectDto()); + + Set<ProjectDto> projectDto = Set.of(publicProject.getProjectDto(), privateProject.getProjectDto()); + List<ProjectDto> projectDtos = mockProjectAnalysisTokenUserSession(user, privateProject.getProjectDto()).keepAuthorizedEntities(permission, projectDto); + + assertThat(projectDtos).containsExactly(privateProject.getProjectDto()) + .doesNotContain(privateProjectWithoutAccess.getProjectDto()); + } + + @Test + public void keepAuthorizedEntities_shouldFilterPrivateProjects_whenUserToken() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + ProjectData privateProjectWithoutAccess = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, USER, privateProject.getProjectDto()); + + Set<ProjectDto> projectDto = Set.of(publicProject.getProjectDto(), privateProject.getProjectDto()); + List<ProjectDto> projectDtos = mockTokenUserSession(user).keepAuthorizedEntities(USER, projectDto); + + assertThat(projectDtos).containsExactlyInAnyOrder(privateProject.getProjectDto(), publicProject.getProjectDto()) + .doesNotContain(privateProjectWithoutAccess.getProjectDto()); + } + + @Test + public void keepAuthorizedEntities_shouldFilterPrivateProjects_returnEmptyListForPermissionOtherThanScanOrBrowse() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, CODEVIEWER, privateProject.getProjectDto()); + + Set<ProjectDto> projectDto = Set.of(publicProject.getProjectDto(), privateProject.getProjectDto()); + List<ProjectDto> projectDtos = mockProjectAnalysisTokenUserSession(user, privateProject.getProjectDto()).keepAuthorizedEntities(CODEVIEWER, projectDto); + + assertThat(projectDtos).isEmpty(); + } + + @Test + public void keepAuthorizedEntities_shouldFailForUnsupportedTokenSession() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, USER, privateProject.getProjectDto()); + + Set<ProjectDto> projectDto = Set.of(publicProject.getProjectDto(), privateProject.getProjectDto()); + + TokenUserSession tokenUserSession = mockProjectBadgeTokenSession(user); + assertThatThrownBy(() -> tokenUserSession.keepAuthorizedEntities(USER, projectDto)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported token type PROJECT_BADGE_TOKEN"); + } + + @Test + public void keepAuthorizedComponents_shouldFilterProjects_whenGlobalAnalysisToken() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + ProjectData privateProjectWithoutAccess = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, USER, privateProject.getProjectDto()); + + Set<ComponentDto> componentDtos = Set.of(publicProject.getMainBranchComponent(), privateProject.getMainBranchComponent()); + List<ComponentDto> authorizedComponents = mockGlobalAnalysisTokenUserSession(user).keepAuthorizedComponents(USER, componentDtos); + + assertThat(authorizedComponents).containsExactlyInAnyOrder(privateProject.getMainBranchComponent(), publicProject.getMainBranchComponent()) + .doesNotContain(privateProjectWithoutAccess.getMainBranchComponent()); + } + + @Test + @UseDataProvider("validPermissions") + public void keepAuthorizedComponents_shouldFilterPrivateProjects_whenProjectAnalysisToken(String permission) { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + ProjectData privateProjectWithoutAccess = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, permission, privateProject.getProjectDto()); + + Set<ComponentDto> componentDtos = Set.of(publicProject.getMainBranchComponent(), privateProject.getMainBranchComponent()); + List<ComponentDto> authorizedComponents = mockProjectAnalysisTokenUserSession(user, privateProject.getProjectDto()) + .keepAuthorizedComponents(permission, componentDtos); + + assertThat(authorizedComponents).containsExactly(privateProject.getMainBranchComponent()) + .doesNotContain(privateProjectWithoutAccess.getMainBranchComponent(), publicProject.getMainBranchComponent()); + } + + @Test + public void keepAuthorizedComponents_shouldFilterPrivateProjects_whenUserToken() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + ProjectData privateProjectWithoutAccess = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, USER, privateProject.getProjectDto()); + + Set<ComponentDto> componentDtos = Set.of(publicProject.getMainBranchComponent(), privateProject.getMainBranchComponent()); + List<ComponentDto> authorizedComponents = mockTokenUserSession(user).keepAuthorizedComponents(USER, componentDtos); + + assertThat(authorizedComponents).containsExactlyInAnyOrder(privateProject.getMainBranchComponent(), publicProject.getMainBranchComponent()) + .doesNotContain(privateProjectWithoutAccess.getMainBranchComponent()); + } + + @Test + public void keepAuthorizedComponents_returnEmptyListForPermissionOtherThanScanOrBrowse() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, UserRole.CODEVIEWER, privateProject.getProjectDto()); + + Set<ComponentDto> componentDtos = Set.of(publicProject.getMainBranchComponent(), privateProject.getMainBranchComponent()); + List<ComponentDto> authorizedComponents = mockProjectAnalysisTokenUserSession(user, privateProject.getProjectDto()) + .keepAuthorizedComponents(UserRole.CODEVIEWER, componentDtos); + + assertThat(authorizedComponents).isEmpty(); + } + + @Test + public void keepAuthorizedComponents_shouldFailForUnsupportedTokenSession() { + UserDto user = db.users().insertUser(); + + ProjectData publicProject = db.components().insertPublicProject(); + ProjectData privateProject = db.components().insertPrivateProject(); + + db.users().insertProjectPermissionOnUser(user, USER, privateProject.getProjectDto()); + + Set<ComponentDto> componentDtos = Set.of(publicProject.getMainBranchComponent(), privateProject.getMainBranchComponent()); + + TokenUserSession tokenUserSession = mockProjectBadgeTokenSession(user); + assertThatThrownBy(() -> tokenUserSession.keepAuthorizedComponents(USER, componentDtos)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unsupported token type PROJECT_BADGE_TOKEN"); + } + + @DataProvider + public static Object[][] validPermissions() { + return new Object[][] { + {USER}, + {SCAN}, + }; + } + + @Test + public void getType_shouldReturnValidTypeOfToken() { + UserDto user = db.users().insertUser(); + ProjectData privateProject = db.components().insertPrivateProject(); + + TokenUserSession projectBadgeTokenSession = mockProjectBadgeTokenSession(user); + assertThat(projectBadgeTokenSession.getTokenType()).isEqualTo(PROJECT_BADGE_TOKEN); + + TokenUserSession tokenUserSession = mockTokenUserSession(user); + assertThat(tokenUserSession.getTokenType()).isEqualTo(USER_TOKEN); + + TokenUserSession projectAnalysisTokenUserSession = mockProjectAnalysisTokenUserSession(user, privateProject.getProjectDto()); + assertThat(projectAnalysisTokenUserSession.getTokenType()).isEqualTo(PROJECT_ANALYSIS_TOKEN); + + TokenUserSession globalAnalysisTokenUserSession = mockGlobalAnalysisTokenUserSession(user); + assertThat(globalAnalysisTokenUserSession.getTokenType()).isEqualTo(GLOBAL_ANALYSIS_TOKEN); + } + private TokenUserSession mockTokenUserSession(UserDto userDto) { return new TokenUserSession(dbClient, userDto, mockUserTokenDto()); } @@ -191,6 +401,10 @@ public class TokenUserSessionIT { return new TokenUserSession(dbClient, userDto, mockGlobalAnalysisTokenDto()); } + private TokenUserSession mockProjectBadgeTokenSession(UserDto userDto) { + return new TokenUserSession(dbClient, userDto, mockBadgeTokenDto()); + } + private static UserTokenDto mockUserTokenDto() { UserTokenDto userTokenDto = new UserTokenDto(); userTokenDto.setType(USER_TOKEN.name()); @@ -218,4 +432,12 @@ public class TokenUserSessionIT { return userTokenDto; } + private static UserTokenDto mockBadgeTokenDto() { + UserTokenDto userTokenDto = new UserTokenDto(); + userTokenDto.setType(PROJECT_BADGE_TOKEN.name()); + userTokenDto.setName("Badge token"); + userTokenDto.setUserUuid("userUid"); + return userTokenDto; + } + } 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 461c0f106dd..9074b5d5cc9 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 @@ -142,7 +142,7 @@ public abstract class AbstractUserSession implements UserSession { } @Override - public <T extends EntityDto> List<T> keepAuthorizedEntities(String permission, Collection<T> projects) { + public final <T extends EntityDto> List<T> keepAuthorizedEntities(String permission, Collection<T> projects) { return doKeepAuthorizedEntities(permission, projects); } 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 bbfe6d96e7e..036529f7b91 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 @@ -187,7 +187,7 @@ public class ServerUserSession extends AbstractUserSession { } @Override - public <T extends EntityDto> List<T> keepAuthorizedEntities(String permission, Collection<T> entities) { + protected <T extends EntityDto> List<T> doKeepAuthorizedEntities(String permission, Collection<T> entities) { Set<String> projectsUuids = entities.stream().map(EntityDto::getUuid).collect(Collectors.toSet()); // TODO in SONAR-19445 Set<String> authorizedEntitiesUuids = keepEntitiesUuidsByPermission(permission, projectsUuids); @@ -364,7 +364,7 @@ public class ServerUserSession extends AbstractUserSession { Set<String> allProjectUuids = new HashSet<>(projectUuids); allProjectUuids.addAll(originalComponentsProjectUuids); - Set<String> authorizedProjectUuids = dbClient.authorizationDao().keepAuthorizedEntityUuids(dbSession, allProjectUuids, getUuid(), permission); + Set<String> authorizedProjectUuids = keepAuthorizedProjectsUuids(dbSession, permission, allProjectUuids); return components.stream() .filter(c -> { @@ -380,6 +380,10 @@ public class ServerUserSession extends AbstractUserSession { } } + protected Set<String> keepAuthorizedProjectsUuids(DbSession dbSession, String permission, Collection<String> entityUuids) { + return dbClient.authorizationDao().keepAuthorizedEntityUuids(dbSession, entityUuids, getUuid(), permission); + } + private Map<String, ComponentDto> findComponentsByCopyComponentUuid(Collection<ComponentDto> components, DbSession dbSession) { Set<String> copyComponentsUuid = components.stream() .map(ComponentDto::getCopyComponentUuid) diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java index e105fd4950b..30a5abdca3e 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java @@ -19,16 +19,25 @@ */ package org.sonar.server.user; +import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; +import java.util.List; import java.util.Set; import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.entity.EntityDto; import org.sonar.db.permission.GlobalPermission; import org.sonar.db.user.TokenType; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; +import static java.lang.String.format; +import static org.sonar.api.web.UserRole.USER; + public class TokenUserSession extends ServerUserSession { + private static final String TOKEN_ASSERTION_ERROR_MESSAGE = "Unsupported token type %s"; private static final String SCAN = "scan"; private static final Set<GlobalPermission> GLOBAL_ANALYSIS_TOKEN_SUPPORTED_PERMISSIONS = EnumSet.of(GlobalPermission.SCAN, GlobalPermission.PROVISION_PROJECTS); private final UserTokenDto userToken; @@ -41,41 +50,70 @@ public class TokenUserSession extends ServerUserSession { @Override protected boolean hasEntityUuidPermission(String permission, String entityUuid) { TokenType tokenType = TokenType.valueOf(userToken.getType()); - switch (tokenType) { - case USER_TOKEN: - return super.hasEntityUuidPermission(permission, entityUuid); - case PROJECT_ANALYSIS_TOKEN: - return SCAN.equals(permission) && - entityUuid.equals(userToken.getProjectUuid()) && - (super.hasEntityUuidPermission(SCAN, entityUuid) || super.hasPermissionImpl(GlobalPermission.SCAN)); - case GLOBAL_ANALYSIS_TOKEN: - //The case with a global analysis token has to return false always, since it is based on the assumption that the user + return switch (tokenType) { + case USER_TOKEN -> super.hasEntityUuidPermission(permission, entityUuid); + case PROJECT_ANALYSIS_TOKEN -> SCAN.equals(permission) && + entityUuid.equals(userToken.getProjectUuid()) && + (super.hasEntityUuidPermission(SCAN, entityUuid) || super.hasPermissionImpl(GlobalPermission.SCAN)); + case GLOBAL_ANALYSIS_TOKEN -> + // The case with a global analysis token has to return false always, since it is based on the assumption that the user // has global analysis privileges - return false; - default: - throw new IllegalArgumentException("Unsupported token type " + tokenType.name()); - } + false; + default -> throw new IllegalArgumentException(format(TOKEN_ASSERTION_ERROR_MESSAGE, tokenType.name())); + }; } @Override protected boolean hasPermissionImpl(GlobalPermission permission) { TokenType tokenType = TokenType.valueOf(userToken.getType()); - switch (tokenType) { - case USER_TOKEN: - return super.hasPermissionImpl(permission); - case PROJECT_ANALYSIS_TOKEN: - //The case with a project analysis token has to return false always, delegating the result to the super class would allow - //the project analysis token to work for multiple projects in case the user has Global Permissions. - return false; - case GLOBAL_ANALYSIS_TOKEN: - return GLOBAL_ANALYSIS_TOKEN_SUPPORTED_PERMISSIONS.contains(permission) && super.hasPermissionImpl(permission); - default: - throw new IllegalArgumentException("Unsupported token type " + tokenType.name()); - } + return switch (tokenType) { + case USER_TOKEN -> super.hasPermissionImpl(permission); + case PROJECT_ANALYSIS_TOKEN -> + // The case with a project analysis token has to return false always, delegating the result to the super class would allow + // the project analysis token to work for multiple projects in case the user has Global Permissions. + false; + case GLOBAL_ANALYSIS_TOKEN -> + GLOBAL_ANALYSIS_TOKEN_SUPPORTED_PERMISSIONS.contains(permission) && super.hasPermissionImpl(permission); + default -> throw new IllegalArgumentException(format(TOKEN_ASSERTION_ERROR_MESSAGE, tokenType.name())); + }; + } + + @Override + protected <T extends EntityDto> List<T> doKeepAuthorizedEntities(String permission, Collection<T> entities) { + TokenType tokenType = TokenType.valueOf(userToken.getType()); + return switch (tokenType) { + case USER_TOKEN, GLOBAL_ANALYSIS_TOKEN -> super.doKeepAuthorizedEntities(permission, entities); + case PROJECT_ANALYSIS_TOKEN -> + (SCAN.equals(permission) || USER.equals(permission)) ? entities.stream() + .filter(entity -> entity.getUuid().equals(userToken.getProjectUuid())) + .toList() : Collections.emptyList(); + default -> throw new IllegalArgumentException(format(TOKEN_ASSERTION_ERROR_MESSAGE, tokenType.name())); + }; + } + + /** + * Required to override doKeepAuthorizedComponents to handle the case of a project analysis token + */ + @Override + protected Set<String> keepAuthorizedProjectsUuids(DbSession dbSession, String permission, Collection<String> entityUuids) { + TokenType tokenType = TokenType.valueOf(userToken.getType()); + return switch (tokenType) { + case USER_TOKEN, GLOBAL_ANALYSIS_TOKEN -> super.keepAuthorizedProjectsUuids(dbSession, permission, entityUuids); + case PROJECT_ANALYSIS_TOKEN -> + (SCAN.equals(permission) || USER.equals(permission)) ? Collections.singleton(userToken.getProjectUuid()) : Collections.emptySet(); + default -> throw new IllegalArgumentException(format(TOKEN_ASSERTION_ERROR_MESSAGE, tokenType.name())); + }; } public UserTokenDto getUserToken() { return userToken; } + + /** + * @return the type of the token, based on the {@link TokenType} enum + */ + public TokenType getTokenType() { + return TokenType.valueOf(userToken.getType()); + } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java index f7744ed7dc4..ca96953776b 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java @@ -41,6 +41,7 @@ import org.sonar.api.resources.Qualifiers; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; import org.sonar.core.platform.EditionProvider.Edition; import org.sonar.core.platform.PlatformEditionProvider; import org.sonar.db.DbClient; @@ -130,12 +131,12 @@ public class SearchProjectsActionIT { @DataProvider public static Object[][] rating_metric_keys() { - return new Object[][]{{SQALE_RATING_KEY}, {RELIABILITY_RATING_KEY}, {SECURITY_RATING_KEY}}; + return new Object[][] {{SQALE_RATING_KEY}, {RELIABILITY_RATING_KEY}, {SECURITY_RATING_KEY}}; } @DataProvider public static Object[][] software_quality_rating_metric_keys() { - return new Object[][]{{SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {SOFTWARE_QUALITY_RELIABILITY_RATING_KEY}, + return new Object[][] {{SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {SOFTWARE_QUALITY_RELIABILITY_RATING_KEY}, {SOFTWARE_QUALITY_SECURITY_RATING_KEY}}; } @@ -149,12 +150,12 @@ public class SearchProjectsActionIT { @DataProvider public static Object[][] new_rating_metric_keys() { - return new Object[][]{{NEW_MAINTAINABILITY_RATING_KEY}, {NEW_RELIABILITY_RATING_KEY}, {NEW_SECURITY_RATING_KEY}}; + return new Object[][] {{NEW_MAINTAINABILITY_RATING_KEY}, {NEW_RELIABILITY_RATING_KEY}, {NEW_SECURITY_RATING_KEY}}; } @DataProvider public static Object[][] new_software_quality_rating_metric_keys() { - return new Object[][]{{NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY}, + return new Object[][] {{NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY}}; } @@ -168,17 +169,17 @@ public class SearchProjectsActionIT { @DataProvider public static Object[][] component_qualifiers_for_valid_editions() { - return new Object[][]{ - {new String[]{Qualifiers.PROJECT}, Edition.COMMUNITY}, - {new String[]{Qualifiers.APP, Qualifiers.PROJECT}, Edition.DEVELOPER}, - {new String[]{Qualifiers.APP, Qualifiers.PROJECT}, Edition.ENTERPRISE}, - {new String[]{Qualifiers.APP, Qualifiers.PROJECT}, Edition.DATACENTER}, + return new Object[][] { + {new String[] {Qualifiers.PROJECT}, Edition.COMMUNITY}, + {new String[] {Qualifiers.APP, Qualifiers.PROJECT}, Edition.DEVELOPER}, + {new String[] {Qualifiers.APP, Qualifiers.PROJECT}, Edition.ENTERPRISE}, + {new String[] {Qualifiers.APP, Qualifiers.PROJECT}, Edition.DATACENTER}, }; } @DataProvider public static Object[][] community_or_developer_edition() { - return new Object[][]{ + return new Object[][] { {Edition.COMMUNITY}, {Edition.DEVELOPER}, }; @@ -186,25 +187,25 @@ public class SearchProjectsActionIT { @DataProvider public static Object[][] enterprise_or_datacenter_edition() { - return new Object[][]{ + return new Object[][] { {Edition.ENTERPRISE}, {Edition.DATACENTER}, }; } - private DbClient dbClient = db.getDbClient(); - private DbSession dbSession = db.getSession(); + private final DbClient dbClient = db.getDbClient(); + private final DbSession dbSession = db.getSession(); - private PlatformEditionProvider editionProviderMock = mock(PlatformEditionProvider.class); - private PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(dbClient, + private final PlatformEditionProvider editionProviderMock = mock(PlatformEditionProvider.class); + private final PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(dbClient, es.client())); - private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), + private final ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE); - private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client()); + private final ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client()); - private WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, editionProviderMock)); + private final WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, editionProviderMock)); - private RequestBuilder request = SearchProjectsRequest.builder(); + private final RequestBuilder request = SearchProjectsRequest.builder(); @Test public void verify_definition() { @@ -260,8 +261,7 @@ public class SearchProjectsActionIT { Param facets = def.param("facets"); assertThat(facets.defaultValue()).isNull(); - assertThat(facets.possibleValues()).containsOnly("ncloc", "duplicated_lines_density", "coverage", "sqale_rating", "reliability_rating" - , "security_rating", "alert_status", + assertThat(facets.possibleValues()).containsOnly("ncloc", "duplicated_lines_density", "coverage", "sqale_rating", "reliability_rating", "security_rating", "alert_status", "languages", "tags", "qualifier", "new_reliability_rating", "new_security_rating", "new_maintainability_rating", "new_coverage", "new_duplicated_lines_density", "new_lines", "security_review_rating", "security_hotspots_reviewed", "new_security_hotspots_reviewed", "new_security_review_rating", @@ -1370,12 +1370,14 @@ public class SearchProjectsActionIT { @Test public void return_visibility_flag() { userSession.logIn(); - ProjectDto privateProject = db.components().insertPublicProject(componentDto -> componentDto.setName("proj_A")).getProjectDto(); + ProjectDto privateProject = db.components().insertPrivateProject(componentDto -> componentDto.setName("proj_A")).getProjectDto(); authorizationIndexerTester.allowOnlyAnyone(privateProject); - ProjectDto publicProject = db.components().insertPrivateProject(componentDto -> componentDto.setName("proj_B")).getProjectDto(); + ProjectDto publicProject = db.components().insertPublicProject(componentDto -> componentDto.setName("proj_B")).getProjectDto(); authorizationIndexerTester.allowOnlyAnyone(publicProject); index(); + userSession.addProjectPermission(UserRole.USER, privateProject); + SearchProjectsWsResponse result = call(request); assertThat(result.getComponentsList()).extracting(Component::getKey, Component::getVisibility) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java index f6b51f76de4..6d4c8ccb05e 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java @@ -46,6 +46,7 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.web.UserRole; import org.sonar.core.platform.EditionProvider.Edition; import org.sonar.core.platform.PlatformEditionProvider; import org.sonar.db.DbClient; @@ -55,6 +56,7 @@ import org.sonar.db.component.SnapshotDto; import org.sonar.db.project.ProjectDto; import org.sonar.db.property.PropertyDto; import org.sonar.db.property.PropertyQuery; +import org.sonar.db.user.TokenType; import org.sonar.server.component.ws.FilterParser.Criterion; import org.sonar.server.component.ws.SearchProjectsAction.SearchResults.SearchResultsBuilder; import org.sonar.server.es.Facets; @@ -63,6 +65,7 @@ import org.sonar.server.es.SearchOptions; import org.sonar.server.measure.index.ProjectMeasuresIndex; import org.sonar.server.measure.index.ProjectMeasuresQuery; import org.sonar.server.project.Visibility; +import org.sonar.server.user.TokenUserSession; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Common; import org.sonarqube.ws.Components.Component; @@ -236,12 +239,15 @@ public class SearchProjectsAction implements ComponentsWsAction { ProjectMeasuresQueryValidator.validate(query); SearchIdResult<String> esResults = index.search(query, new SearchOptions() - .addFacets(request.getFacets()) + // skip facets for project token authorization, avoid exposing unauthorized projects count + .addFacets(isProjectAnalysisToken() ? emptyList() : request.getFacets()) .setPage(request.getPage(), request.getPageSize())); List<String> projectUuids = esResults.getUuids(); Ordering<ProjectDto> ordering = Ordering.explicit(projectUuids).onResultOf(ProjectDto::getUuid); List<ProjectDto> projects = ordering.immutableSortedCopy(dbClient.projectDao().selectByUuids(dbSession, new HashSet<>(projectUuids))); + projects = userSession.keepAuthorizedEntities(UserRole.USER, projects); + Map<String, BranchDto> mainBranchByUuid = dbClient.branchDao().selectMainBranchesByProjectUuids(dbSession, projectUuids) .stream() .collect(Collectors.toMap(BranchDto::getUuid, b -> b)); @@ -281,7 +287,7 @@ public class SearchProjectsAction implements ComponentsWsAction { private Set<String> getQualifiersFromEdition() { Optional<Edition> edition = editionProvider.get(); - if (!edition.isPresent()) { + if (edition.isEmpty()) { return Sets.newHashSet(Qualifiers.PROJECT); } @@ -367,7 +373,8 @@ public class SearchProjectsAction implements ComponentsWsAction { .map(response -> response.setPaging(Common.Paging.newBuilder() .setPageIndex(request.getPage()) .setPageSize(request.getPageSize()) - .setTotal(searchResults.total))) + // skip total for project token authorization, avoid exposing unauthorized projects count + .setTotal(isProjectAnalysisToken() ? searchResults.projects.size() : searchResults.total))) .map(response -> { searchResults.projects.stream() .map(dbToWsComponent) @@ -690,4 +697,11 @@ public class SearchProjectsAction implements ComponentsWsAction { return new SearchProjectsRequest(this); } } + + private boolean isProjectAnalysisToken() { + if (userSession instanceof TokenUserSession tokenUserSession) { + return TokenType.PROJECT_ANALYSIS_TOKEN.equals(tokenUserSession.getTokenType()); + } + return false; + } } |