]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23070 Fix SSF-635
authorJacek Poreda <jacek.poreda@sonarsource.com>
Tue, 17 Sep 2024 12:59:33 +0000 (14:59 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 24 Sep 2024 20:02:54 +0000 (20:02 +0000)
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java

index adff5727a824429a41889f1ff527f851648b3911..9a69f7c5e3ca3915c049b030662f728885eb3b36 100644 (file)
@@ -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);
   }
 
index 8c46370819aa6c23959881cf3a49fc69d66fbbef..e58e07e1ebb2afd2b9b1c6b6b4c450c93be14ed8 100644 (file)
@@ -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)
index ffa05172d7a4e92bb5f02164b9a05ce831c3fbd4..2e665eef08bcfa5c11876e8874e4dc0f6070d54c 100644 (file)
  */
 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());
+  }
 }
index 75213f72a4484954cf99ae1e5ec244a4e2f07438..3df6febcc0249c4a99ebe9176a0523563afbf24a 100644 (file)
  */
 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;
+  }
+
 }
index a2e8b11ea63f415ec22e2765143515b1a38e414a..74f3355eecee301818153e33952d989111a2e15d 100644 (file)
@@ -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;
+  }
 }
index 0f32005b93bd9d2fb4544e9f85e023ee0f57b422..160ce5c35f88022296646958c37698773a42b7d8 100644 (file)
@@ -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)