aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorJacek Poreda <jacek.poreda@sonarsource.com>2024-09-17 14:59:33 +0200
committersonartech <sonartech@sonarsource.com>2024-09-20 20:02:37 +0000
commit747d4ede9457dcf3dd0e89d080913f3e24396361 (patch)
tree7f09834ed423c2a2acaa50a00096d3cc6d6c2e1b /server
parent300226052a9c81c3246472e645087580b201824e (diff)
downloadsonarqube-747d4ede9457dcf3dd0e89d080913f3e24396361.tar.gz
sonarqube-747d4ede9457dcf3dd0e89d080913f3e24396361.zip
SONAR-23070 Fix SSF-635
Diffstat (limited to 'server')
-rw-r--r--server/sonar-webserver-auth/src/it/java/org/sonar/server/user/TokenUserSessionIT.java246
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java2
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java8
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java88
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java48
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java20
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;
+ }
}