aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacek Poreda <jacek.poreda@sonarsource.com>2024-09-17 14:59:33 +0200
committersonartech <sonartech@sonarsource.com>2024-09-24 20:02:54 +0000
commita139d1dc705cfcccfa39ab5263fb37ff8d9cf3c6 (patch)
tree9aad487cef5cdc3492c9d63569ffd0f5eb6570f0
parente61a5bcaba82f2c256f3745cb2dc9fdaf0a0c2b2 (diff)
downloadsonarqube-a139d1dc705cfcccfa39ab5263fb37ff8d9cf3c6.tar.gz
sonarqube-a139d1dc705cfcccfa39ab5263fb37ff8d9cf3c6.zip
SONAR-23070 Fix SSF-635
-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.java84
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java233
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java21
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java43
6 files changed, 339 insertions, 52 deletions
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 adff5727a82..9a69f7c5e3c 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
@@ -141,7 +141,7 @@ public abstract class AbstractUserSession implements UserSession {
}
@Override
- public List<ProjectDto> keepAuthorizedProjects(String permission, Collection<ProjectDto> projects) {
+ public final List<ProjectDto> keepAuthorizedProjects(String permission, Collection<ProjectDto> projects) {
return doKeepAuthorizedProjects(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 8c46370819a..e58e07e1ebb 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
@@ -182,7 +182,7 @@ public class ServerUserSession extends AbstractUserSession {
}
@Override
- public List<ProjectDto> keepAuthorizedProjects(String permission, Collection<ProjectDto> projects) {
+ protected List<ProjectDto> doKeepAuthorizedProjects(String permission, Collection<ProjectDto> projects) {
Set<String> projectsUuids = projects.stream().map(ProjectDto::getUuid).collect(Collectors.toSet());
Set<String> authorizedProjectsUuids = keepProjectsUuidsByPermission(permission, projectsUuids);
@@ -325,7 +325,7 @@ public class ServerUserSession extends AbstractUserSession {
Set<String> allProjectUuids = new HashSet<>(projectUuids);
allProjectUuids.addAll(originalComponentsProjectUuids);
- Set<String> authorizedProjectUuids = dbClient.authorizationDao().keepAuthorizedProjectUuids(dbSession, allProjectUuids, getUuid(), permission);
+ Set<String> authorizedProjectUuids = keepAuthorizedProjectsUuids(dbSession, permission, allProjectUuids);
return components.stream()
.filter(c -> {
@@ -341,6 +341,10 @@ public class ServerUserSession extends AbstractUserSession {
}
}
+ protected Set<String> keepAuthorizedProjectsUuids(DbSession dbSession, String permission, Collection<String> entityUuids) {
+ return dbClient.authorizationDao().keepAuthorizedProjectUuids(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 ffa05172d7a..2e665eef08b 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.permission.GlobalPermission;
+import org.sonar.db.project.ProjectDto;
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 hasProjectUuidPermission(String permission, String projectUuid) {
TokenType tokenType = TokenType.valueOf(userToken.getType());
- switch (tokenType) {
- case USER_TOKEN:
- return super.hasProjectUuidPermission(permission, projectUuid);
- case PROJECT_ANALYSIS_TOKEN:
- return SCAN.equals(permission) &&
+ return switch (tokenType) {
+ case USER_TOKEN -> super.hasProjectUuidPermission(permission, projectUuid);
+ case PROJECT_ANALYSIS_TOKEN -> SCAN.equals(permission) &&
projectUuid.equals(userToken.getProjectUuid()) &&
(super.hasProjectUuidPermission(SCAN, projectUuid) || 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
+ 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 List<ProjectDto> doKeepAuthorizedProjects(String permission, Collection<ProjectDto> projects) {
+ TokenType tokenType = TokenType.valueOf(userToken.getType());
+ return switch (tokenType) {
+ case USER_TOKEN, GLOBAL_ANALYSIS_TOKEN -> super.doKeepAuthorizedProjects(permission, projects);
+ case PROJECT_ANALYSIS_TOKEN ->
+ (SCAN.equals(permission) || USER.equals(permission)) ? projects.stream()
+ .filter(project -> project.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-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java
index 75213f72a44..3df6febcc02 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.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.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.component.ComponentDbTester.toProjectDto;
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 TokenUserSessionTest {
@Rule
@@ -82,7 +96,7 @@ public class TokenUserSessionTest {
db.users().insertProjectPermissionOnUser(user, SCAN, project1);
db.users().insertProjectPermissionOnUser(user, SCAN, project2);
- TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1);
+ TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user, project1);
assertThat(userSession.hasProjectUuidPermission(SCAN, project1.branchUuid())).isTrue();
assertThat(userSession.hasProjectUuidPermission(SCAN, project2.branchUuid())).isFalse();
@@ -97,7 +111,7 @@ public class TokenUserSessionTest {
db.users().insertPermissionOnUser(user, GlobalPermission.SCAN);
- TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1);
+ TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user, project1);
assertThat(userSession.hasProjectUuidPermission(SCAN, project1.branchUuid())).isTrue();
assertThat(userSession.hasProjectUuidPermission(SCAN, project2.branchUuid())).isFalse();
@@ -125,7 +139,7 @@ public class TokenUserSessionTest {
db.users().insertPermissionOnUser(user, GlobalPermission.SCAN);
- TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1);
+ TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user, project1);
assertThat(userSession.hasPermission(GlobalPermission.SCAN)).isFalse();
}
@@ -178,6 +192,207 @@ public class TokenUserSessionTest {
assertThat(userSession.hasPermission(GlobalPermission.ADMINISTER)).isFalse();
}
+ @Test
+ public void keepAuthorizedEntities_shouldFilterProjects_whenGlobalAnalysisToken() {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto privateProjectComponent = db.components().insertPrivateProject();
+ ProjectDto publicProject = db.components().insertPublicProjectDto();
+ ProjectDto privateProject = toProjectDto(privateProjectComponent, 0L);
+ ProjectDto privateProjectWithoutAccess = db.components().insertPrivateProjectDto();
+
+ db.users().insertProjectPermissionOnUser(user, USER, privateProjectComponent);
+
+ Set<ProjectDto> projectDto = Set.of(publicProject, privateProject);
+ List<ProjectDto> projectDtos = mockGlobalAnalysisTokenUserSession(user).doKeepAuthorizedProjects(USER, projectDto);
+
+ assertThat(projectDtos).containsExactlyInAnyOrder(privateProject, publicProject)
+ .doesNotContain(privateProjectWithoutAccess);
+ }
+
+ @Test
+ @UseDataProvider("validPermissions")
+ public void keepAuthorizedEntities_shouldFilterPrivateProjects_whenProjectAnalysisToken(String permission) {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto privateProjectComponent = db.components().insertPrivateProject();
+ ProjectDto publicProject = db.components().insertPublicProjectDto();
+ ProjectDto privateProject = toProjectDto(privateProjectComponent, 0L);
+ ProjectDto privateProjectWithoutAccess = db.components().insertPrivateProjectDto();
+
+ db.users().insertProjectPermissionOnUser(user, permission, privateProjectComponent);
+
+ Set<ProjectDto> projectDto = Set.of(publicProject, privateProject);
+ List<ProjectDto> projectDtos = mockProjectAnalysisTokenUserSession(user, privateProjectComponent).keepAuthorizedProjects(permission, projectDto);
+
+ assertThat(projectDtos).containsExactly(privateProject)
+ .doesNotContain(privateProjectWithoutAccess);
+ }
+
+ @Test
+ public void keepAuthorizedEntities_shouldFilterPrivateProjects_whenUserToken() {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto privateProjectComponent = db.components().insertPrivateProject();
+ ProjectDto publicProject = db.components().insertPublicProjectDto();
+ ProjectDto privateProject = toProjectDto(privateProjectComponent, 0L);
+ ProjectDto privateProjectWithoutAccess = db.components().insertPrivateProjectDto();
+
+ db.users().insertProjectPermissionOnUser(user, USER, privateProjectComponent);
+
+ Set<ProjectDto> projectDto = Set.of(publicProject, privateProject);
+ List<ProjectDto> projectDtos = mockTokenUserSession(user).keepAuthorizedProjects(USER, projectDto);
+
+ assertThat(projectDtos).containsExactlyInAnyOrder(privateProject, publicProject)
+ .doesNotContain(privateProjectWithoutAccess);
+ }
+
+ @Test
+ public void keepAuthorizedEntities_shouldFilterPrivateProjects_returnEmptyListForPermissionOtherThanScanOrBrowse() {
+ UserDto user = db.users().insertUser();
+
+ ProjectDto publicProject = db.components().insertPublicProjectDto();
+ ComponentDto privateProjectComponent = db.components().insertPrivateProject();
+ ProjectDto privateProject = toProjectDto(privateProjectComponent, 0L);
+
+ db.users().insertProjectPermissionOnUser(user, CODEVIEWER, privateProjectComponent);
+
+ Set<ProjectDto> projectDto = Set.of(publicProject, privateProject);
+ List<ProjectDto> projectDtos = mockProjectAnalysisTokenUserSession(user, privateProjectComponent).keepAuthorizedProjects(CODEVIEWER, projectDto);
+
+ assertThat(projectDtos).isEmpty();
+ }
+
+ @Test
+ public void keepAuthorizedEntities_shouldFailForUnsupportedTokenSession() {
+ UserDto user = db.users().insertUser();
+
+ ProjectDto publicProject = db.components().insertPublicProjectDto();
+ ComponentDto privateProjectComponent = db.components().insertPrivateProject();
+ ProjectDto privateProject = toProjectDto(privateProjectComponent, 0L);
+
+ db.users().insertProjectPermissionOnUser(user, USER, privateProjectComponent);
+
+ Set<ProjectDto> projectDto = Set.of(publicProject, privateProject);
+
+ TokenUserSession tokenUserSession = mockProjectBadgeTokenSession(user);
+ assertThatThrownBy(() -> tokenUserSession.keepAuthorizedProjects(USER, projectDto))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Unsupported token type PROJECT_BADGE_TOKEN");
+ }
+
+ @Test
+ public void keepAuthorizedComponents_shouldFilterProjects_whenGlobalAnalysisToken() {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto publicProject = db.components().insertPublicProject();
+ ComponentDto privateProject = db.components().insertPrivateProject();
+ ComponentDto privateProjectWithoutAccess = db.components().insertPrivateProject();
+
+ db.users().insertProjectPermissionOnUser(user, USER, privateProject);
+
+ Set<ComponentDto> componentDtos = Set.of(publicProject, privateProject);
+ List<ComponentDto> authorizedComponents = mockGlobalAnalysisTokenUserSession(user).keepAuthorizedComponents(USER, componentDtos);
+
+ assertThat(authorizedComponents).containsExactlyInAnyOrder(privateProject, publicProject)
+ .doesNotContain(privateProjectWithoutAccess);
+ }
+
+ @Test
+ @UseDataProvider("validPermissions")
+ public void keepAuthorizedComponents_shouldFilterPrivateProjects_whenProjectAnalysisToken(String permission) {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto publicProject = db.components().insertPublicProject();
+ ComponentDto privateProject = db.components().insertPrivateProject();
+ ComponentDto privateProjectWithoutAccess = db.components().insertPrivateProject();
+
+ db.users().insertProjectPermissionOnUser(user, permission, privateProject);
+
+ Set<ComponentDto> componentDtos = Set.of(publicProject, privateProject);
+ List<ComponentDto> authorizedComponents = mockProjectAnalysisTokenUserSession(user, privateProject)
+ .keepAuthorizedComponents(permission, componentDtos);
+
+ assertThat(authorizedComponents).containsExactly(privateProject)
+ .doesNotContain(privateProjectWithoutAccess, publicProject);
+ }
+
+ @Test
+ public void keepAuthorizedComponents_shouldFilterPrivateProjects_whenUserToken() {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto publicProject = db.components().insertPublicProject();
+ ComponentDto privateProject = db.components().insertPrivateProject();
+ ComponentDto privateProjectWithoutAccess = db.components().insertPrivateProject();
+
+ db.users().insertProjectPermissionOnUser(user, USER, privateProject);
+
+ Set<ComponentDto> componentDtos = Set.of(publicProject, privateProject);
+ List<ComponentDto> authorizedComponents = mockTokenUserSession(user).keepAuthorizedComponents(USER, componentDtos);
+
+ assertThat(authorizedComponents).containsExactlyInAnyOrder(privateProject, publicProject)
+ .doesNotContain(privateProjectWithoutAccess);
+ }
+
+ @Test
+ public void keepAuthorizedComponents_returnEmptyListForPermissionOtherThanScanOrBrowse() {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto publicProject = db.components().insertPublicProject();
+ ComponentDto privateProject = db.components().insertPrivateProject();
+
+ db.users().insertProjectPermissionOnUser(user, UserRole.CODEVIEWER, privateProject);
+
+ Set<ComponentDto> componentDtos = Set.of(publicProject, privateProject);
+ List<ComponentDto> authorizedComponents = mockProjectAnalysisTokenUserSession(user, privateProject)
+ .keepAuthorizedComponents(UserRole.CODEVIEWER, componentDtos);
+
+ assertThat(authorizedComponents).isEmpty();
+ }
+
+ @Test
+ public void keepAuthorizedComponents_shouldFailForUnsupportedTokenSession() {
+ UserDto user = db.users().insertUser();
+
+ ComponentDto publicProject = db.components().insertPublicProject();
+ ComponentDto privateProject = db.components().insertPrivateProject();
+
+ db.users().insertProjectPermissionOnUser(user, USER, privateProject);
+
+ Set<ComponentDto> componentDtos = Set.of(publicProject, privateProject);
+
+ 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();
+ ComponentDto privateComponent = 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, privateComponent);
+ 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());
}
@@ -190,6 +405,10 @@ public class TokenUserSessionTest {
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());
@@ -217,4 +436,12 @@ public class TokenUserSessionTest {
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-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 a2e8b11ea63..74f3355eece 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
@@ -45,6 +45,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.core.util.stream.MoreCollectors;
@@ -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;
@@ -65,6 +67,7 @@ import org.sonar.server.measure.index.ProjectMeasuresIndex;
import org.sonar.server.measure.index.ProjectMeasuresQuery;
import org.sonar.server.project.Visibility;
import org.sonar.server.qualitygate.ProjectsInWarning;
+import org.sonar.server.user.TokenUserSession;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Common;
import org.sonarqube.ws.Components.Component;
@@ -73,6 +76,7 @@ import org.sonarqube.ws.Components.SearchProjectsWsResponse;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
@@ -243,12 +247,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.keepAuthorizedProjects(UserRole.USER, projects);
+
Map<String, SnapshotDto> analysisByProjectUuid = getSnapshots(dbSession, request, projectUuids);
Map<String, Long> applicationsLeakPeriod = getApplicationsLeakPeriod(dbSession, request, qualifiersBasedOnEdition, projectUuids);
@@ -287,7 +294,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);
}
@@ -377,7 +384,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)
@@ -719,4 +727,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;
+ }
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
index 0f32005b93b..160ce5c35f8 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java
@@ -42,6 +42,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;
@@ -126,27 +127,27 @@ public class SearchProjectsActionTest {
@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[][] 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[][] 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},
};
@@ -154,25 +155,25 @@ public class SearchProjectsActionTest {
@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, es.client()));
- private ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
- private ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
- private ProjectsInWarning projectsInWarning = new ProjectsInWarning();
+ private final PlatformEditionProvider editionProviderMock = mock(PlatformEditionProvider.class);
+ private final PermissionIndexerTester authorizationIndexerTester = new PermissionIndexerTester(es, new ProjectMeasuresIndexer(dbClient, es.client()));
+ private final ProjectMeasuresIndex index = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
+ private final ProjectMeasuresIndexer projectMeasuresIndexer = new ProjectMeasuresIndexer(db.getDbClient(), es.client());
+ private final ProjectsInWarning projectsInWarning = new ProjectsInWarning();
- private WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, projectsInWarning, editionProviderMock,
+ private final WsActionTester ws = new WsActionTester(new SearchProjectsAction(dbClient, index, userSession, projectsInWarning, editionProviderMock,
new IssueIndexSyncProgressChecker(db.getDbClient())));
- private RequestBuilder request = SearchProjectsRequest.builder();
+ private final RequestBuilder request = SearchProjectsRequest.builder();
@Before
public void setUp() {
@@ -1265,12 +1266,14 @@ public class SearchProjectsActionTest {
@Test
public void return_visibility_flag() {
userSession.logIn();
- ComponentDto privateProject = db.components().insertPublicProject();
+ ComponentDto privateProject = db.components().insertPrivateProject();
authorizationIndexerTester.allowOnlyAnyone(privateProject);
- ComponentDto publicProject = db.components().insertPrivateProject();
+ ComponentDto publicProject = db.components().insertPublicProject();
authorizationIndexerTester.allowOnlyAnyone(publicProject);
index();
+ userSession.addProjectPermission(UserRole.USER, privateProject);
+
SearchProjectsWsResponse result = call(request);
assertThat(result.getComponentsList()).extracting(Component::getKey, Component::getVisibility)