]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15702 Return '403' in case WEB API called without access to all application...
authorJacek <jacek.poreda@sonarsource.com>
Tue, 30 Nov 2021 10:59:21 +0000 (11:59 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 3 Dec 2021 20:03:33 +0000 (20:03 +0000)
- api/measures/component_tree
- api/measures/search_history
- api/qualitygates/application_status
- api/applications/show_leak
- api/project_analysis/search

server/sonar-webserver-auth/src/main/java/org/sonar/server/user/AbstractUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/SearchHistoryAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/projectanalysis/ws/SearchAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/measure/ws/SearchHistoryActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java

index 3d36461a0e5fd897e874b79d36608c728e18cd0c..c99021614777f1343f55dce80bf0eb76456d6f4c 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
 
 import static org.apache.commons.lang.StringUtils.defaultString;
+import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.server.user.UserSession.IdentityProvider.SONARQUBE;
 
 public abstract class AbstractUserSession implements UserSession {
@@ -109,7 +110,16 @@ public abstract class AbstractUserSession implements UserSession {
     if (isRoot()) {
       return true;
     }
-    return hasChildProjectsPermission(permission, component.uuid());
+    String applicationUuid = defaultString(component.getMainBranchProjectUuid(), component.projectUuid());
+    return hasChildProjectsPermission(permission, applicationUuid);
+  }
+
+  @Override
+  public final boolean hasChildProjectsPermission(String permission, ProjectDto project) {
+    if (isRoot()) {
+      return true;
+    }
+    return hasChildProjectsPermission(permission, project.getUuid());
   }
 
   @Override
@@ -208,12 +218,20 @@ public abstract class AbstractUserSession implements UserSession {
 
   @Override
   public UserSession checkChildProjectsPermission(String projectPermission, ComponentDto component) {
-    if (isRoot() || !component.qualifier().equals(Qualifiers.APP) || hasChildProjectsPermission(projectPermission, component.uuid())) {
+    if (isRoot() || !APP.equals(component.qualifier()) || hasChildProjectsPermission(projectPermission, component)) {
       return this;
     }
 
     throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
+  }
+
+  @Override
+  public UserSession checkChildProjectsPermission(String projectPermission, ProjectDto application) {
+    if (isRoot() || !APP.equals(application.getQualifier()) || hasChildProjectsPermission(projectPermission, application)) {
+      return this;
+    }
 
+    throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE);
   }
 
   @Override
index 8102efd755fc0b904534e599e81446b9f511bfb3..2584b0ac0c20847ec57973af0517c98363ef35b1 100644 (file)
@@ -150,6 +150,12 @@ public class ThreadLocalUserSession implements UserSession {
     return this;
   }
 
+  @Override
+  public UserSession checkChildProjectsPermission(String projectPermission, ProjectDto application) {
+    get().checkChildProjectsPermission(projectPermission, application);
+    return this;
+  }
+
   @Override
   public UserSession checkComponentUuidPermission(String permission, String componentUuid) {
     get().checkComponentUuidPermission(permission, componentUuid);
@@ -182,6 +188,11 @@ public class ThreadLocalUserSession implements UserSession {
     return get().hasChildProjectsPermission(permission, component);
   }
 
+  @Override
+  public boolean hasChildProjectsPermission(String permission, ProjectDto project) {
+    return get().hasChildProjectsPermission(permission, project);
+  }
+
   @Override
   public boolean hasComponentUuidPermission(String permission, String componentUuid) {
     return get().hasComponentUuidPermission(permission, componentUuid);
index fd6e8f715563997702bfbe3aa0337e6ac53e3022..157042f8f6cc4bb95070b9454a9132c8fff464c5 100644 (file)
@@ -197,6 +197,8 @@ public interface UserSession {
 
   boolean hasChildProjectsPermission(String permission, ComponentDto component);
 
+  boolean hasChildProjectsPermission(String permission, ProjectDto component);
+
   /**
    * Using {@link #hasComponentPermission(String, ComponentDto)} is recommended
    * because it does not have to load project if the referenced component
@@ -236,6 +238,12 @@ public interface UserSession {
    */
   UserSession checkChildProjectsPermission(String projectPermission, ComponentDto project);
 
+  /**
+   * Ensures that {@link #hasChildProjectsPermission(String, ProjectDto)} is {@code true}
+   * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
+   */
+  UserSession checkChildProjectsPermission(String projectPermission, ProjectDto application);
+
   /**
    * Ensures that {@link #hasComponentUuidPermission(String, String)} is {@code true},
    * otherwise throws a {@link org.sonar.server.exceptions.ForbiddenException}.
index c9ccb3c984df7496ee749b93dc181be42b847548..81abd5180457a8ee92b285707f3d9a9bd39f9827 100644 (file)
@@ -22,9 +22,12 @@ package org.sonar.server.user;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.project.ProjectDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.GroupTesting;
+import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.tester.AnonymousMockUserSession;
 import org.sonar.server.tester.MockUserSession;
@@ -68,6 +71,7 @@ public class ThreadLocalUserSessionTest {
     assertThat(threadLocalUserSession.shouldResetPassword()).isTrue();
     assertThat(threadLocalUserSession.getGroups()).extracting(GroupDto::getUuid).containsOnly(group.getUuid());
     assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ComponentDto())).isFalse();
+    assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ProjectDto())).isFalse();
   }
 
   @Test
@@ -89,4 +93,44 @@ public class ThreadLocalUserSessionTest {
       .isInstanceOf(UnauthorizedException.class);
   }
 
+  @Test
+  public void throw_ForbiddenException_when_no_access_to_applications_projects() {
+    GroupDto group = GroupTesting.newGroupDto();
+    MockUserSession expected = new MockUserSession("karadoc")
+      .setUuid("karadoc-uuid")
+      .setResetPassword(true)
+      .setLastSonarlintConnectionDate(1000L)
+      .setGroups(group);
+    threadLocalUserSession.set(expected);
+
+    ComponentDto componentDto = new ComponentDto().setQualifier(Qualifiers.APP).setMainBranchProjectUuid("component-uuid");
+    ProjectDto projectDto = new ProjectDto().setQualifier(Qualifiers.APP).setUuid("project-uuid");
+    assertThatThrownBy(() -> threadLocalUserSession.checkChildProjectsPermission(USER, componentDto))
+      .isInstanceOf(ForbiddenException.class);
+    assertThatThrownBy(() -> threadLocalUserSession.checkChildProjectsPermission(USER, projectDto))
+      .isInstanceOf(ForbiddenException.class);
+  }
+
+  @Test
+  public void checkChildProjectsPermission_gets_session_when_user_has_access_to_applications_projects() {
+    GroupDto group = GroupTesting.newGroupDto();
+    MockUserSession expected = new MockUserSession("karadoc")
+      .setUuid("karadoc-uuid")
+      .setResetPassword(true)
+      .setLastSonarlintConnectionDate(1000L)
+      .setGroups(group);
+
+    ProjectDto subProjectDto = new ProjectDto().setQualifier(Qualifiers.PROJECT).setUuid("subproject-uuid");
+    ComponentDto applicationAsComponentDto = new ComponentDto().setQualifier(Qualifiers.APP).setUuid("application-component-uuid").setProjectUuid("application-project-uuid");
+    ProjectDto applicationAsProjectDto = new ProjectDto().setQualifier(Qualifiers.APP).setUuid("application-project-uuid");
+
+    expected.registerProjects(subProjectDto);
+    expected.registerApplication(applicationAsProjectDto, subProjectDto);
+    expected.registerComponents(applicationAsComponentDto);
+    threadLocalUserSession.set(expected);
+
+    assertThat(threadLocalUserSession.checkChildProjectsPermission(USER, applicationAsComponentDto)).isEqualTo(threadLocalUserSession);
+    assertThat(threadLocalUserSession.checkChildProjectsPermission(USER, applicationAsProjectDto)).isEqualTo(threadLocalUserSession);
+  }
+
 }
index 4b33d0ff77c2c7d2ecf7f347b16361fffeb7c3d4..c343ae92d193591078edec84a47ebf1b36e0ed98 100644 (file)
@@ -261,6 +261,11 @@ public class UserSessionRule implements TestRule, UserSession {
     return currentUserSession.hasChildProjectsPermission(permission, component);
   }
 
+  @Override
+  public boolean hasChildProjectsPermission(String permission, ProjectDto component) {
+    return currentUserSession.hasChildProjectsPermission(permission, component);
+  }
+
   @Override
   public boolean hasComponentUuidPermission(String permission, String componentUuid) {
     return currentUserSession.hasComponentUuidPermission(permission, componentUuid);
@@ -370,6 +375,12 @@ public class UserSessionRule implements TestRule, UserSession {
     return this;
   }
 
+  @Override
+  public UserSession checkChildProjectsPermission(String projectPermission, ProjectDto application) {
+    currentUserSession.checkChildProjectsPermission(projectPermission, application);
+    return this;
+  }
+
   @Override
   public UserSession checkComponentUuidPermission(String permission, String componentUuid) {
     currentUserSession.checkComponentUuidPermission(permission, componentUuid);
index 194635013037853437ec7e61e8f4fb4fd9b8cc62..66fbe2ce9d7474a1b945e61bc32b270bd0020716 100644 (file)
@@ -46,6 +46,7 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.sonar.api.resources.Qualifiers;
 import org.sonar.api.resources.ResourceTypes;
+import org.sonar.api.resources.Scopes;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -105,8 +106,8 @@ import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriod.snapshotToWsPeri
 import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
-import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
 import static org.sonar.server.ws.WsParameterBuilder.createQualifiersParameter;
+import static org.sonar.server.ws.WsParameterBuilder.QualifierParameterContext.newQualifierParameterContext;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
 
 /**
@@ -402,7 +403,7 @@ public class ComponentTreeAction implements MeasuresWsAction {
       ComponentDto baseComponent = loadComponent(dbSession, wsRequest);
       checkPermissions(baseComponent);
       Optional<SnapshotDto> baseSnapshot = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, baseComponent.projectUuid());
-      if (!baseSnapshot.isPresent()) {
+      if (baseSnapshot.isEmpty()) {
         return ComponentTreeData.builder()
           .setBaseComponent(baseComponent)
           .build();
@@ -625,6 +626,10 @@ public class ComponentTreeAction implements MeasuresWsAction {
 
   private void checkPermissions(ComponentDto baseComponent) {
     userSession.checkComponentPermission(UserRole.USER, baseComponent);
+
+    if (Scopes.PROJECT.equals(baseComponent.scope()) && Qualifiers.APP.equals(baseComponent.qualifier())) {
+      userSession.checkChildProjectsPermission(UserRole.USER, baseComponent);
+    }
   }
 
   public static boolean isFileComponent(@Nonnull ComponentDto input) {
index 44ecf17aec5c994fcc3dad0ecea9b739755f1cac..9cca3a1a218bae9f24ac9b6276518ccac2b4cb1c 100644 (file)
@@ -27,6 +27,8 @@ import java.util.function.Function;
 import java.util.stream.Stream;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
@@ -166,6 +168,9 @@ public class SearchHistoryAction implements MeasuresWsAction {
   private ComponentDto searchComponent(SearchHistoryRequest request, DbSession dbSession) {
     ComponentDto component = loadComponent(dbSession, request);
     userSession.checkComponentPermission(UserRole.USER, component);
+    if (Scopes.PROJECT.equals(component.scope()) && Qualifiers.APP.equals(component.qualifier())) {
+      userSession.checkChildProjectsPermission(UserRole.USER, component);
+    }
     return component;
   }
 
index 706fa91a7f0925502d73d6d8b805902cef4442a3..745a50aa694857eaee5add9122a199196917710f 100644 (file)
@@ -177,6 +177,9 @@ public class SearchAction implements ProjectAnalysesWsAction {
 
   private void checkPermission(ComponentDto project) {
     userSession.checkComponentPermission(UserRole.USER, project);
+    if (Scopes.PROJECT.equals(project.scope()) && Qualifiers.APP.equals(project.qualifier())) {
+      userSession.checkChildProjectsPermission(UserRole.USER, project);
+    }
   }
 
   private void addProject(SearchData.Builder data) {
index 8f467a74fc42e6c906d088e5dc1283e77cca0aa3..b25b1751ef7f2fba246eb0968bdbcffb4854ed64 100644 (file)
@@ -70,6 +70,7 @@ import static org.sonar.api.resources.Qualifiers.UNIT_TEST_FILE;
 import static org.sonar.api.server.ws.WebService.Param.SORT;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.db.component.BranchType.PULL_REQUEST;
+import static org.sonar.db.component.ComponentDbTester.toProjectDto;
 import static org.sonar.db.component.ComponentTesting.newDirectory;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newProjectCopy;
@@ -833,12 +834,32 @@ public class ComponentTreeActionTest {
     ComponentDto project = db.components().insertPrivateProject();
     db.components().insertSnapshot(project);
 
-    assertThatThrownBy(() -> {
-      ws.newRequest()
-        .setParam(PARAM_COMPONENT, project.getKey())
-        .setParam(PARAM_METRIC_KEYS, "ncloc")
-        .executeProtobuf(ComponentTreeWsResponse.class);
-    })
+    var request = ws.newRequest()
+      .setParam(PARAM_COMPONENT, project.getKey())
+      .setParam(PARAM_METRIC_KEYS, "ncloc");
+    assertThatThrownBy(() -> request.executeProtobuf(ComponentTreeWsResponse.class))
+      .isInstanceOf(ForbiddenException.class);
+  }
+
+  @Test
+  public void fail_when_app_with_insufficient_privileges_for_projects() {
+    userSession.logIn();
+    ComponentDto app = db.components().insertPrivateApplication();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    db.components().insertSnapshot(app);
+
+    userSession.registerApplication(
+      toProjectDto(app, 1L),
+      toProjectDto(project1, 1L),
+      toProjectDto(project2, 1L));
+
+    userSession.addProjectPermission(UserRole.USER, app, project1);
+
+    var request = ws.newRequest()
+      .setParam(PARAM_COMPONENT, app.getKey())
+      .setParam(PARAM_METRIC_KEYS, "ncloc");
+    assertThatThrownBy(() -> request.executeProtobuf(ComponentTreeWsResponse.class))
       .isInstanceOf(ForbiddenException.class);
   }
 
index 0375a2c9f0dbd97c8fb09256cceceecd4795ab8a..be355c219f47c1d99231358fa1d75e0b603cbdcb 100644 (file)
@@ -60,6 +60,7 @@ import static org.assertj.core.api.Assertions.tuple;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.db.component.BranchType.PULL_REQUEST;
+import static org.sonar.db.component.ComponentDbTester.toProjectDto;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
 import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
@@ -347,8 +348,8 @@ public class SearchHistoryActionTest {
       .setParam(PARAM_COMPONENT, branch.getDbKey())
       .setParam(PARAM_METRICS, "ncloc")
       .execute())
-      .isInstanceOf(NotFoundException.class)
-      .hasMessageContaining(format("Component key '%s' not found", branch.getDbKey()));
+        .isInstanceOf(NotFoundException.class)
+        .hasMessageContaining(format("Component key '%s' not found", branch.getDbKey()));
   }
 
   @Test
@@ -375,6 +376,28 @@ public class SearchHistoryActionTest {
       .isInstanceOf(ForbiddenException.class);
   }
 
+  @Test
+  public void fail_if_not_enough_permissions_for_application() {
+    ComponentDto application = db.components().insertPrivateApplication();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    userSession.logIn()
+      .registerApplication(
+        toProjectDto(application, 1L),
+        toProjectDto(project1, 1L),
+        toProjectDto(project2, 1L))
+      .addProjectPermission(UserRole.USER, application, project1);
+
+    SearchHistoryRequest request = SearchHistoryRequest.builder()
+      .setComponent(application.getDbKey())
+      .setMetrics(singletonList(complexityMetric.getKey()))
+      .build();
+
+    assertThatThrownBy(() -> call(request))
+      .isInstanceOf(ForbiddenException.class);
+  }
+
   @Test
   public void fail_if_unknown_component() {
     SearchHistoryRequest request = SearchHistoryRequest.builder()
@@ -396,8 +419,8 @@ public class SearchHistoryActionTest {
       .setParam(PARAM_COMPONENT, "file-key")
       .setParam(PARAM_METRICS, "ncloc")
       .execute())
-      .isInstanceOf(NotFoundException.class)
-      .hasMessageContaining("Component key 'file-key' not found");
+        .isInstanceOf(NotFoundException.class)
+        .hasMessageContaining("Component key 'file-key' not found");
   }
 
   @Test
@@ -412,8 +435,8 @@ public class SearchHistoryActionTest {
       .setParam(PARAM_BRANCH, "another_branch")
       .setParam(PARAM_METRICS, "ncloc")
       .execute())
-      .isInstanceOf(NotFoundException.class)
-      .hasMessageContaining(String.format("Component '%s' on branch '%s' not found", file.getKey(), "another_branch"));
+        .isInstanceOf(NotFoundException.class)
+        .hasMessageContaining(String.format("Component '%s' on branch '%s' not found", file.getKey(), "another_branch"));
   }
 
   @Test
index d2b6938ac082c22a73bcc02f5921152680d24206..25b6bb4a82fa5d78d2c2aaa65ee1ec447a2f0761 100644 (file)
@@ -72,6 +72,7 @@ import static org.sonar.api.utils.DateUtils.formatDate;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.db.component.ComponentDbTester.toProjectDto;
 import static org.sonar.db.component.ComponentTesting.newBranchDto;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.SnapshotTesting.newAnalysis;
@@ -155,7 +156,8 @@ public class SearchActionTest {
     db.events().insertEvent(newEvent(a1).setUuid("AXt91FkXy_c4CIP4ds6A")
       .setName("Failed")
       .setCategory(QUALITY_GATE.getLabel())
-      .setDescription("Coverage on New Code < 85, Reliability Rating > 4, Maintainability Rating on New Code > 1, Reliability Rating on New Code > 1, Security Rating on New Code > 1, Duplicated Lines (%) on New Code > 3"));
+      .setDescription(
+        "Coverage on New Code < 85, Reliability Rating > 4, Maintainability Rating on New Code > 1, Reliability Rating on New Code > 1, Security Rating on New Code > 1, Duplicated Lines (%) on New Code > 3"));
     db.events().insertEvent(newEvent(a1).setUuid("AXx_QFJ6Wa8wkfuJ6r5P")
       .setName("6.3").setCategory(VERSION.getLabel()));
     db.events().insertEvent(newEvent(a2).setUuid("E21")
@@ -234,7 +236,7 @@ public class SearchActionTest {
   @Test
   public void return_analyses_of_application() {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     SnapshotDto secondAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(2_000_000L));
     SnapshotDto thirdAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(3_000_000L));
@@ -253,7 +255,7 @@ public class SearchActionTest {
   @Test
   public void return_definition_change_events_on_application_analyses() {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     EventDto event = db.events().insertEvent(newEvent(firstAnalysis).setName("").setUuid("E11").setCategory(DEFINITION_CHANGE.getLabel()));
     EventComponentChangeDto changeDto1 = generateEventComponentChange(event, ADDED, "My project", "app1", "master", uuidFactoryFast.create());
@@ -278,7 +280,7 @@ public class SearchActionTest {
   @UseDataProvider("changedBranches")
   public void application_definition_change_with_branch(@Nullable String oldBranch, @Nullable String newBranch) {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     EventDto event = db.events().insertEvent(newEvent(firstAnalysis).setName("").setUuid("E11").setCategory(DEFINITION_CHANGE.getLabel()));
     EventComponentChangeDto changeDto1 = generateEventComponentChange(event, REMOVED, "My project", "app1", oldBranch, uuidFactoryFast.create());
@@ -300,7 +302,7 @@ public class SearchActionTest {
   @Test
   public void incorrect_eventcomponentchange_two_identical_changes_added_on_same_project() {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     EventDto event = db.events().insertEvent(newEvent(firstAnalysis).setName("").setUuid("E11").setCategory(DEFINITION_CHANGE.getLabel()));
     EventComponentChangeDto changeDto1 = generateEventComponentChange(event, ADDED, "My project", "app1", "master", uuidFactoryFast.create());
@@ -329,7 +331,7 @@ public class SearchActionTest {
   @Test
   public void incorrect_eventcomponentchange_incorrect_category() {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     EventDto event = db.events().insertEvent(newEvent(firstAnalysis).setName("").setUuid("E11").setCategory(DEFINITION_CHANGE.getLabel()));
     EventComponentChangeDto changeDto1 = generateEventComponentChange(event, FAILED_QUALITY_GATE, "My project", "app1", "master", uuidFactoryFast.create());
@@ -355,7 +357,7 @@ public class SearchActionTest {
   @Test
   public void incorrect_eventcomponentchange_three_component_changes_on_same_project() {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     EventDto event = db.events().insertEvent(newEvent(firstAnalysis).setName("").setUuid("E11").setCategory(DEFINITION_CHANGE.getLabel()));
     EventComponentChangeDto changeDto1 = generateEventComponentChange(event, ADDED, "My project", "app1", "master", uuidFactoryFast.create());
@@ -386,7 +388,7 @@ public class SearchActionTest {
   @Test
   public void incorrect_quality_gate_information() {
     ComponentDto application = db.components().insertPublicApplication();
-    userSession.registerComponents(application);
+    userSession.registerApplication(toProjectDto(application, 1L));
     SnapshotDto firstAnalysis = db.components().insertSnapshot(newAnalysis(application).setCreatedAt(1_000_000L));
     EventDto event = db.events().insertEvent(
       newEvent(firstAnalysis)
@@ -639,6 +641,24 @@ public class SearchActionTest {
       .isInstanceOf(ForbiddenException.class);
   }
 
+  @Test
+  public void fail_if_not_enough_permissions_on_applications_projects() {
+    ComponentDto application = db.components().insertPrivateApplication();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    userSession.logIn()
+      .registerApplication(
+        toProjectDto(application, 1L),
+        toProjectDto(project1, 1L),
+        toProjectDto(project2, 1L))
+      .addProjectPermission(UserRole.USER, application, project1);
+
+    var projectDbKey = application.getDbKey();
+    assertThatThrownBy(() -> call(projectDbKey))
+      .isInstanceOf(ForbiddenException.class);
+  }
+
   @Test
   public void fail_if_project_does_not_exist() {
     assertThatThrownBy(() -> call("P1"))