aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java5
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java5
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ServerUserSession.java5
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java5
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java2
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java1
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java1
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java11
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java1
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AnonymousMockUserSession.java5
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java5
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java5
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java5
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java8
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java3
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java9
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidator.java72
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java31
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java27
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushClientTest.java15
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidatorTest.java94
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientTest.java24
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java71
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java3
24 files changed, 363 insertions, 50 deletions
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java
index 78601b04878..6864bce8424 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SafeModeUserSession.java
@@ -108,4 +108,9 @@ public class SafeModeUserSession extends AbstractUserSession {
public boolean isSystemAdministrator() {
return false;
}
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
}
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java
index 0e4e8a70aff..0ac01236152 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/DoPrivileged.java
@@ -142,6 +142,11 @@ public final class DoPrivileged {
return true;
}
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
}
private void start() {
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 9d51a52211c..b8d9dfa21b5 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
@@ -342,6 +342,11 @@ public class ServerUserSession extends AbstractUserSession {
return isSystemAdministrator;
}
+ @Override
+ public boolean isActive() {
+ return userDto.isActive();
+ }
+
private boolean loadIsSystemAdministrator() {
if (isRoot()) {
return true;
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
index 1ae45a0fa90..1d8866c1b16 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/ThreadLocalUserSession.java
@@ -174,6 +174,11 @@ public class ThreadLocalUserSession implements UserSession {
}
@Override
+ public boolean isActive() {
+ return get().isActive();
+ }
+
+ @Override
public boolean hasComponentPermission(String permission, ComponentDto component) {
return get().hasComponentPermission(permission, component);
}
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
index 116dc6341c0..f10db0d1dfc 100644
--- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
+++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSession.java
@@ -274,4 +274,6 @@ public interface UserSession {
* otherwise throws {@link org.sonar.server.exceptions.ForbiddenException}.
*/
UserSession checkIsSystemAdministrator();
+
+ boolean isActive();
}
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java
index ad1cc032f00..0278a2eab3f 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SafeModeUserSessionTest.java
@@ -37,6 +37,7 @@ public class SafeModeUserSessionTest {
assertThat(underTest.shouldResetPassword()).isFalse();
assertThat(underTest.getName()).isNull();
assertThat(underTest.getGroups()).isEmpty();
+ assertThat(underTest.isActive()).isFalse();
}
@Test
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java
index 9275a3ce5ed..b45fe1547d6 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/DoPrivilegedTest.java
@@ -51,6 +51,7 @@ public class DoPrivilegedTest {
assertThat(catcher.userSession.hasComponentPermission("any permission", new ComponentDto())).isTrue();
assertThat(catcher.userSession.isSystemAdministrator()).isTrue();
assertThat(catcher.userSession.shouldResetPassword()).isFalse();
+ assertThat(catcher.userSession.isActive()).isTrue();
assertThat(catcher.userSession.hasChildProjectsPermission(USER, new ComponentDto())).isTrue();
assertThat(catcher.userSession.hasPortfolioChildProjectsPermission(USER, new ComponentDto())).isTrue();
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
index c43ee7ee538..c3ea59725e7 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
@@ -127,6 +127,17 @@ public class ServerUserSessionTest {
}
@Test
+ public void isActive_redirectsValueFromUserDto() {
+ UserDto active = db.users().insertUser();
+ active.setActive(true);
+ assertThat(newUserSession(active).isActive()).isTrue();
+
+ UserDto notActive = db.users().insertUser();
+ notActive.setActive(false);
+ assertThat(newUserSession(notActive).isActive()).isFalse();
+ }
+
+ @Test
public void isRoot_is_false_is_flag_root_is_false_on_UserDto() {
UserDto root = db.users().insertUser();
root = db.users().makeRoot(root);
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
index c831d7bfe24..4f8a5e463c9 100644
--- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
+++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/ThreadLocalUserSessionTest.java
@@ -69,6 +69,7 @@ public class ThreadLocalUserSessionTest {
assertThat(threadLocalUserSession.getLogin()).isEqualTo("karadoc");
assertThat(threadLocalUserSession.getUuid()).isEqualTo("karadoc-uuid");
assertThat(threadLocalUserSession.isLoggedIn()).isTrue();
+ assertThat(threadLocalUserSession.isActive()).isTrue();
assertThat(threadLocalUserSession.shouldResetPassword()).isTrue();
assertThat(threadLocalUserSession.getGroups()).extracting(GroupDto::getUuid).containsOnly(group.getUuid());
assertThat(threadLocalUserSession.hasChildProjectsPermission(USER, new ComponentDto())).isFalse();
diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AnonymousMockUserSession.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AnonymousMockUserSession.java
index b1495fe6f22..704345cf6eb 100644
--- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AnonymousMockUserSession.java
+++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/AnonymousMockUserSession.java
@@ -36,6 +36,11 @@ public class AnonymousMockUserSession extends AbstractMockUserSession<AnonymousM
}
@Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
public String getLogin() {
return null;
}
diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
index ecc6f906844..b199c30275a 100644
--- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
+++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/MockUserSession.java
@@ -86,6 +86,11 @@ public class MockUserSession extends AbstractMockUserSession<MockUserSession> {
return root;
}
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
public void setRoot(boolean root) {
this.root = root;
}
diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
index c50fdfbb4b0..c8fc0b37f98 100644
--- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
+++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/tester/UserSessionRule.java
@@ -412,4 +412,9 @@ public class UserSessionRule implements TestRule, UserSession {
currentUserSession.checkIsSystemAdministrator();
return this;
}
+
+ @Override
+ public boolean isActive() {
+ return currentUserSession.isActive();
+ }
}
diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java
index 892ed01a0d9..d09d9ea662d 100644
--- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java
+++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java
@@ -134,6 +134,11 @@ public class TestUserSessionFactory implements UserSessionFactory {
throw notImplemented();
}
+ @Override
+ public boolean isActive() {
+ throw notImplemented();
+ }
+
private static RuntimeException notImplemented() {
return new UnsupportedOperationException("not implemented");
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java
index a726ee1197f..458b716a3af 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushClient.java
@@ -83,9 +83,13 @@ public abstract class ServerPushClient {
close();
}
- private synchronized void close() {
+ public synchronized void close() {
startedHeartbeat.cancel(false);
- asyncContext.complete();
+ try {
+ asyncContext.complete();
+ } catch (IllegalStateException ex) {
+ LOG.trace("Push connection was already closed");
+ }
}
private ServletOutputStream output() throws IOException {
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java
index c6f194b5414..0a48485466c 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/ServerPushWsModule.java
@@ -20,6 +20,7 @@
package org.sonar.server.pushapi;
import org.sonar.core.platform.Module;
+import org.sonar.server.pushapi.sonarlint.SonarLintClientPermissionsValidator;
import org.sonar.server.pushapi.sonarlint.SonarLintClientsRegistry;
import org.sonar.server.pushapi.sonarlint.SonarLintPushAction;
@@ -29,7 +30,7 @@ public class ServerPushWsModule extends Module {
protected void configureModule() {
add(
ServerPushWs.class,
-
+ SonarLintClientPermissionsValidator.class,
SonarLintClientsRegistry.class,
SonarLintPushAction.class);
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java
index 93512a974ec..3d0f5cff07f 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClient.java
@@ -33,10 +33,13 @@ public class SonarLintClient extends ServerPushClient {
private final Set<String> languages;
private final Set<String> projectKeys;
- public SonarLintClient(AsyncContext asyncContext, Set<String> projectKeys, Set<String> languages) {
+ private final String userUuid;
+
+ public SonarLintClient(AsyncContext asyncContext, Set<String> projectKeys, Set<String> languages, String userUuid) {
super(scheduledExecutorService, asyncContext);
this.projectKeys = projectKeys;
this.languages = languages;
+ this.userUuid = userUuid;
}
public Set<String> getLanguages() {
@@ -65,4 +68,8 @@ public class SonarLintClient extends ServerPushClient {
public int hashCode() {
return Objects.hash(languages, projectKeys);
}
+
+ public String getUserUuid() {
+ return userUuid;
+ }
}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidator.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidator.java
new file mode 100644
index 00000000000..d8765332e90
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidator.java
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.pushapi.sonarlint;
+
+import java.util.List;
+import java.util.Set;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.user.UserSessionFactory;
+
+@ServerSide
+public class SonarLintClientPermissionsValidator {
+
+ private final DbClient dbClient;
+ private final UserSessionFactory userSessionFactory;
+
+ public SonarLintClientPermissionsValidator(DbClient dbClient, UserSessionFactory userSessionFactory) {
+ this.dbClient = dbClient;
+ this.userSessionFactory = userSessionFactory;
+ }
+
+ public void validateUserCanReceivePushEventForProjects(UserSession userSession, Set<String> projectKeys) {
+ List<ProjectDto> projectDtos;
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ projectDtos = dbClient.projectDao().selectProjectsByKeys(dbSession, projectKeys);
+ }
+ validateUsersDeactivationStatus(userSession);
+ for (ProjectDto projectDto : projectDtos) {
+ userSession.checkProjectPermission(UserRole.USER, projectDto);
+ }
+ }
+
+ public void validateUserCanReceivePushEventForProjects(String userUUID, Set<String> projectKeys) {
+ UserDto userDto;
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ userDto = dbClient.userDao().selectByUuid(dbSession, userUUID);
+ }
+ if (userDto == null) {
+ throw new ForbiddenException("User does not exist");
+ }
+ validateUserCanReceivePushEventForProjects(userSessionFactory.create(userDto), projectKeys);
+ }
+
+ private static void validateUsersDeactivationStatus(UserSession userSession) {
+ if (!userSession.isActive()) {
+ throw new ForbiddenException("User doesn't have rights to requested resource anymore.");
+ }
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java
index 51146f28510..bcf88efcad5 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistry.java
@@ -21,7 +21,9 @@ package org.sonar.server.pushapi.sonarlint;
import java.io.IOException;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import javax.servlet.AsyncEvent;
@@ -36,6 +38,7 @@ import org.sonar.core.util.RuleActivationListener;
import org.sonar.core.util.RuleChange;
import org.sonar.core.util.RuleSetChangeEvent;
import org.sonar.server.pushapi.qualityprofile.RuleActivatorEventsDistributor;
+import org.sonar.server.exceptions.ForbiddenException;
import static java.util.Arrays.asList;
@@ -45,12 +48,13 @@ public class SonarLintClientsRegistry implements RuleActivationListener {
private static final Logger LOG = Loggers.get(SonarLintClientsRegistry.class);
private final RuleActivatorEventsDistributor ruleActivatorEventsDistributor;
+ private final SonarLintClientPermissionsValidator sonarLintClientPermissionsValidator;
- public SonarLintClientsRegistry(RuleActivatorEventsDistributor ruleActivatorEventsDistributor) {
+ public SonarLintClientsRegistry(RuleActivatorEventsDistributor ruleActivatorEventsDistributor, SonarLintClientPermissionsValidator permissionsValidator) {
this.ruleActivatorEventsDistributor = ruleActivatorEventsDistributor;
+ this.sonarLintClientPermissionsValidator = permissionsValidator;
}
-
private final List<SonarLintClient> clients = new CopyOnWriteArrayList<>();
public void registerClient(SonarLintClient sonarLintClient) {
@@ -63,6 +67,7 @@ public class SonarLintClientsRegistry implements RuleActivationListener {
}
public void unregisterClient(SonarLintClient client) {
+ client.close();
clients.remove(client);
LOG.debug("Removing SonarLint client");
}
@@ -79,15 +84,25 @@ public class SonarLintClientsRegistry implements RuleActivationListener {
private static Predicate<SonarLintClient> getFilterForEvent(RuleSetChangeEvent ruleChangeEvent) {
List<String> affectedProjects = asList(ruleChangeEvent.getProjects());
- return f -> !Collections.disjoint(f.getClientProjectKeys(), affectedProjects) && f.getLanguages().contains(ruleChangeEvent.getLanguage());
+ return client -> {
+ Set<String> clientProjectKeys = client.getClientProjectKeys();
+ Set<String> languages = client.getLanguages();
+ return !Collections.disjoint(clientProjectKeys, affectedProjects) && languages.contains(ruleChangeEvent.getLanguage());
+ };
}
-
- public void broadcastMessage(RuleSetChangeEvent event, Predicate<SonarLintClient> projectsFilter) {
- clients.stream().filter(projectsFilter).forEach(c -> {
+ public void broadcastMessage(RuleSetChangeEvent message, Predicate<SonarLintClient> filter) {
+ clients.stream().filter(filter).forEach(c -> {
+ Set<String> projectKeysInterestingForClient = new HashSet<>(c.getClientProjectKeys());
+ projectKeysInterestingForClient.retainAll(Set.of(message.getProjects()));
try {
- String jsonString = getJSONString(event);
+ sonarLintClientPermissionsValidator.validateUserCanReceivePushEventForProjects(c.getUserUuid(), projectKeysInterestingForClient);
+ RuleSetChangeEvent personalizedEvent = new RuleSetChangeEvent(projectKeysInterestingForClient.toArray(String[]::new), message.getActivatedRules(),
+ message.getDeactivatedRules());
+ String jsonString = getJSONString(personalizedEvent);
c.writeAndFlush(jsonString);
+ } catch (ForbiddenException forbiddenException) {
+ unregisterClient(c);
} catch (IOException e) {
LOG.error("Unable to send message to a client: " + e.getMessage());
}
@@ -158,7 +173,7 @@ public class SonarLintClientsRegistry implements RuleActivationListener {
@Override
public void onStartAsync(AsyncEvent event) {
- //nothing to do on start
+ // nothing to do on start
}
@Override
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
index ca30b37f478..6a63de5f20a 100644
--- a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/sonarlint/SonarLintPushAction.java
@@ -27,7 +27,6 @@ import javax.servlet.http.HttpServletResponse;
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.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.project.ProjectDto;
@@ -41,13 +40,16 @@ public class SonarLintPushAction extends ServerPushAction {
private static final String PROJECT_PARAM_KEY = "projectKeys";
private static final String LANGUAGE_PARAM_KEY = "languages";
private final SonarLintClientsRegistry clientsRegistry;
+ private final SonarLintClientPermissionsValidator permissionsValidator;
private final UserSession userSession;
private final DbClient dbClient;
- public SonarLintPushAction(SonarLintClientsRegistry sonarLintClientRegistry, UserSession userSession, DbClient dbClient) {
+ public SonarLintPushAction(SonarLintClientsRegistry sonarLintClientRegistry, UserSession userSession, DbClient dbClient,
+ SonarLintClientPermissionsValidator permissionsValidator) {
this.clientsRegistry = sonarLintClientRegistry;
this.userSession = userSession;
this.dbClient = dbClient;
+ this.permissionsValidator = permissionsValidator;
}
@Override
@@ -80,7 +82,9 @@ public class SonarLintPushAction extends ServerPushAction {
ServletResponse servletResponse = (ServletResponse) response;
var params = new SonarLintPushActionParamsValidator(request);
- params.validateProjectsPermissions();
+ params.validateParams();
+
+ permissionsValidator.validateUserCanReceivePushEventForProjects(userSession, params.projectKeys);
if (!isServerSideEventsRequest(servletRequest)) {
servletResponse.stream().setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
@@ -92,7 +96,7 @@ public class SonarLintPushAction extends ServerPushAction {
AsyncContext asyncContext = servletRequest.startAsync();
asyncContext.setTimeout(0);
- SonarLintClient sonarLintClient = new SonarLintClient(asyncContext, params.getProjectKeys(), params.getLanguages());
+ SonarLintClient sonarLintClient = new SonarLintClient(asyncContext, params.getProjectKeys(), params.getLanguages(), userSession.getUuid());
clientsRegistry.registerClient(sonarLintClient);
}
@@ -103,8 +107,6 @@ public class SonarLintPushAction extends ServerPushAction {
private final Set<String> projectKeys;
private final Set<String> languages;
- private List<ProjectDto> projectDtos;
-
SonarLintPushActionParamsValidator(Request request) {
this.request = request;
this.projectKeys = parseParam(PROJECT_PARAM_KEY);
@@ -127,19 +129,16 @@ public class SonarLintPushAction extends ServerPushAction {
return Set.of(paramProjectKeys.trim().split(","));
}
- public void validateProjectsPermissions() {
- if (projectDtos == null) {
- try (DbSession dbSession = dbClient.openSession(false)) {
- projectDtos = dbClient.projectDao().selectProjectsByKeys(dbSession, projectKeys);
- }
+ private void validateParams() {
+ List<ProjectDto> projectDtos;
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ projectDtos = dbClient.projectDao().selectProjectsByKeys(dbSession, projectKeys);
}
if (projectDtos.size() < projectKeys.size() || projectDtos.isEmpty()) {
throw new IllegalArgumentException("Param " + PROJECT_PARAM_KEY + " is invalid.");
}
- for (ProjectDto projectDto : projectDtos) {
- userSession.checkProjectPermission(UserRole.USER, projectDto);
- }
}
+
}
}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushClientTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushClientTest.java
index 8c050f1ec82..47de0e39b9e 100644
--- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushClientTest.java
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/ServerPushClientTest.java
@@ -28,6 +28,7 @@ import javax.servlet.AsyncListener;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
+import org.assertj.core.api.Assertions;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@@ -35,6 +36,7 @@ import org.mockito.Mockito;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,6 +49,7 @@ public class ServerPushClientTest {
private final ServerPushClient underTest = new ServerPushClient(executorService, asyncContext) {};
private final ServletOutputStream outputStream = mock(ServletOutputStream.class);
+ private final ScheduledFuture task = mock(ScheduledFuture.class);
private ServletResponse servletResponse;
@Before
@@ -101,7 +104,6 @@ public class ServerPushClientTest {
@Test
public void write_exceptionCausesConnectionToClose() throws IOException {
when(servletResponse.getOutputStream()).thenThrow(new IOException("mock exception"));
- ScheduledFuture task = mock(ScheduledFuture.class);
when(executorService.schedule(any(HeartbeatTask.class), anyLong(), any(TimeUnit.class))).thenReturn(task);
underTest.scheduleHeartbeat();
@@ -113,7 +115,6 @@ public class ServerPushClientTest {
@Test
public void flush_exceptionCausesConnectionToClose() throws IOException {
when(servletResponse.getOutputStream()).thenThrow(new IOException("mock exception"));
- ScheduledFuture task = mock(ScheduledFuture.class);
when(executorService.schedule(any(HeartbeatTask.class), anyLong(), any(TimeUnit.class))).thenReturn(task);
underTest.scheduleHeartbeat();
@@ -121,4 +122,14 @@ public class ServerPushClientTest {
verify(asyncContext).complete();
}
+
+ @Test
+ public void close_exceptionOnComplete_doesNotThrowException() {
+ when(executorService.schedule(any(HeartbeatTask.class), anyLong(), any(TimeUnit.class))).thenReturn(task);
+ doThrow(new IllegalStateException()).when(asyncContext).complete();
+ underTest.scheduleHeartbeat();
+
+ Assertions.assertThatCode(underTest::close)
+ .doesNotThrowAnyException();
+ }
}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidatorTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidatorTest.java
new file mode 100644
index 00000000000..0dea66827e5
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientPermissionsValidatorTest.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.pushapi.sonarlint;
+
+import java.util.List;
+import java.util.Set;
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.project.ProjectDao;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.user.UserSessionFactory;
+
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SonarLintClientPermissionsValidatorTest {
+
+ private final static String USER_UUID = "USER_UUID";
+
+ private final Set<String> exampleProjectKeys = Set.of("project1", "project2");
+ private final List<ProjectDto> projectDtos = List.of(mock(ProjectDto.class), mock(ProjectDto.class));
+ private final DbClient dbClient = mock(DbClient.class);
+ private final UserSessionFactory userSessionFactory = mock(UserSessionFactory.class);
+ private final UserDao userDao = mock(UserDao.class);
+ private final ProjectDao projectDao = mock(ProjectDao.class);
+ private final UserSession userSession = mock(UserSession.class);
+
+ private SonarLintClientPermissionsValidator underTest = new SonarLintClientPermissionsValidator(dbClient, userSessionFactory);
+
+ @Before
+ public void before() {
+ when(dbClient.userDao()).thenReturn(userDao);
+ when(dbClient.projectDao()).thenReturn(projectDao);
+ when(userSessionFactory.create(any())).thenReturn(userSession);
+ when(projectDao.selectProjectsByKeys(any(), any())).thenReturn(projectDtos);
+ }
+
+ @Test
+ public void validate_givenUserActivatedAndWithRequiredPermissions_dontThrowException() {
+ UserDto userDto = new UserDto();
+ when(userDao.selectByUuid(any(), any())).thenReturn(userDto);
+ when(userSession.isActive()).thenReturn(true);
+
+ assertThatCode(() -> underTest.validateUserCanReceivePushEventForProjects(USER_UUID, exampleProjectKeys))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ public void validate_givenUserNotActivated_throwException() {
+ UserDto userDto = new UserDto();
+ when(userDao.selectByUuid(any(), any())).thenReturn(userDto);
+ when(userSession.isActive()).thenReturn(false);
+
+ assertThrows(ForbiddenException.class,
+ () -> underTest.validateUserCanReceivePushEventForProjects(USER_UUID, exampleProjectKeys));
+ }
+
+ @Test
+ public void validate_givenUserNotGrantedProjectPermissions_throwException() {
+ UserDto userDto = new UserDto();
+ when(userDao.selectByUuid(any(), any())).thenReturn(userDto);
+ when(userSession.isActive()).thenReturn(true);
+ when(userSession.checkProjectPermission(any(), any())).thenThrow(ForbiddenException.class);
+
+ assertThrows(ForbiddenException.class,
+ () -> underTest.validateUserCanReceivePushEventForProjects(USER_UUID, exampleProjectKeys));
+ }
+}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientTest.java
index a99cb7ff571..cc890386a5b 100644
--- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientTest.java
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientTest.java
@@ -31,49 +31,51 @@ public class SonarLintClientTest {
private final AsyncContext firstContext = mock(AsyncContext.class);
private final AsyncContext secondContext = mock(AsyncContext.class);
+ private final String USER_UUID = "userUUID";
+
@Test
public void equals_twoClientsWithSameArgumentsAreEqual() {
- SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of());
- SonarLintClient second = new SonarLintClient(firstContext, Set.of(), Set.of());
+ SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of(), USER_UUID);
+ SonarLintClient second = new SonarLintClient(firstContext, Set.of(), Set.of(), USER_UUID);
assertThat(first).isEqualTo(second);
}
@Test
public void equals_twoClientsWithDifferentAsyncObjects() {
- SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of());
- SonarLintClient second = new SonarLintClient(secondContext, Set.of(), Set.of());
+ SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of(), USER_UUID);
+ SonarLintClient second = new SonarLintClient(secondContext, Set.of(), Set.of(), USER_UUID);
assertThat(first).isNotEqualTo(second);
}
@Test
public void equals_twoClientsWithDifferentLanguages() {
- SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of("java"));
- SonarLintClient second = new SonarLintClient(firstContext, Set.of(), Set.of("cobol"));
+ SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of("java"), USER_UUID);
+ SonarLintClient second = new SonarLintClient(firstContext, Set.of(), Set.of("cobol"), USER_UUID);
assertThat(first).isNotEqualTo(second);
}
@Test
public void equals_twoClientsWithDifferentProjectKeys() {
- SonarLintClient first = new SonarLintClient(firstContext, Set.of("project1", "project2"), Set.of());
- SonarLintClient second = new SonarLintClient(firstContext, Set.of("project1"), Set.of());
+ SonarLintClient first = new SonarLintClient(firstContext, Set.of("project1", "project2"), Set.of(), USER_UUID);
+ SonarLintClient second = new SonarLintClient(firstContext, Set.of("project1"), Set.of(), USER_UUID);
assertThat(first).isNotEqualTo(second);
}
@Test
public void equals_secondClientIsNull() {
- SonarLintClient first = new SonarLintClient(firstContext, Set.of("project1", "project2"), Set.of());
+ SonarLintClient first = new SonarLintClient(firstContext, Set.of("project1", "project2"), Set.of(), USER_UUID);
assertThat(first).isNotEqualTo(null);
}
@Test
public void hashCode_producesSameHashesForEqualObjects() {
- SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of());
- SonarLintClient second = new SonarLintClient(firstContext, Set.of(), Set.of());
+ SonarLintClient first = new SonarLintClient(firstContext, Set.of(), Set.of(), USER_UUID);
+ SonarLintClient second = new SonarLintClient(firstContext, Set.of(), Set.of(), USER_UUID);
assertThat(first).hasSameHashCodeAs(second);
}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java
index c121bf82609..3021e0850b3 100644
--- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintClientsRegistryTest.java
@@ -29,9 +29,14 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.sonar.core.util.RuleChange;
import org.sonar.core.util.RuleSetChangeEvent;
+import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.pushapi.qualityprofile.StandaloneRuleActivatorEventsDistributor;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -42,19 +47,23 @@ public class SonarLintClientsRegistryTest {
private final AsyncContext defaultAsyncContext = mock(AsyncContext.class);
private final Set<String> exampleKeys = Set.of("project1", "project2", "project3");
-
private final Set<String> languageKeys = Set.of("language1", "language2", "language3");
+ private final String USER_UUID = "userUuid";
+ private final ServletResponse response = mock(ServletResponse.class);
+ private final ServletOutputStream outputStream = mock(ServletOutputStream.class);
+
+ private final SonarLintClientPermissionsValidator permissionsValidator = mock(SonarLintClientPermissionsValidator.class);
private SonarLintClientsRegistry underTest;
@Before
public void before() {
- underTest = new SonarLintClientsRegistry(mock(StandaloneRuleActivatorEventsDistributor.class));
+ underTest = new SonarLintClientsRegistry(mock(StandaloneRuleActivatorEventsDistributor.class), permissionsValidator);
}
@Test
public void registerClientAndUnregister_changesNumberOfClients() {
- SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, languageKeys);
+ SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, languageKeys, USER_UUID);
underTest.registerClient(sonarLintClient);
@@ -69,7 +78,7 @@ public class SonarLintClientsRegistryTest {
public void registering10Clients_10ClientsAreRegistered() {
for (int i = 0; i < 10; i++) {
AsyncContext newAsyncContext = mock(AsyncContext.class);
- SonarLintClient sonarLintClient = new SonarLintClient(newAsyncContext, exampleKeys, languageKeys);
+ SonarLintClient sonarLintClient = new SonarLintClient(newAsyncContext, exampleKeys, languageKeys, USER_UUID);
underTest.registerClient(sonarLintClient);
}
@@ -79,11 +88,9 @@ public class SonarLintClientsRegistryTest {
@Test
public void listen_givenOneClientInterestedInJavaEvents_sendOneJavaEvent() throws IOException {
Set<String> javaLanguageKey = Set.of("java");
- ServletResponse response = mock(ServletResponse.class);
when(defaultAsyncContext.getResponse()).thenReturn(response);
- ServletOutputStream outputStream = mock(ServletOutputStream.class);
when(response.getOutputStream()).thenReturn(outputStream);
- SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, javaLanguageKey);
+ SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, javaLanguageKey, USER_UUID);
underTest.registerClient(sonarLintClient);
@@ -103,11 +110,9 @@ public class SonarLintClientsRegistryTest {
@Test
public void listen_givenOneClientInterestedInJsEventsAndJavaEventGenerated_sendZeroEvents() throws IOException {
Set<String> jsLanguageKey = Set.of("js");
- ServletResponse response = mock(ServletResponse.class);
when(defaultAsyncContext.getResponse()).thenReturn(response);
- ServletOutputStream outputStream = mock(ServletOutputStream.class);
when(response.getOutputStream()).thenReturn(outputStream);
- SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, jsLanguageKey);
+ SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, exampleKeys, jsLanguageKey, USER_UUID);
underTest.registerClient(sonarLintClient);
@@ -121,6 +126,52 @@ public class SonarLintClientsRegistryTest {
verifyNoInteractions(outputStream);
}
+ @Test
+ public void listen_givenOneClientInterestedInProjA_DontCheckPermissionsForProjB() throws IOException {
+ when(defaultAsyncContext.getResponse()).thenReturn(response);
+ when(response.getOutputStream()).thenReturn(outputStream);
+ Set<String> clientProjectKeys = Set.of("projA");
+ Set<String> eventProjectKeys = Set.of("projA", "projB");
+ SonarLintClient sonarLintClient = new SonarLintClient(defaultAsyncContext, clientProjectKeys, Set.of("java"), USER_UUID);
+
+ underTest.registerClient(sonarLintClient);
+
+ RuleChange javaRuleChange = createRuleChange("java");
+
+ RuleChange[] activatedRules = {};
+ RuleChange[] deactivatedRules = {javaRuleChange};
+ RuleSetChangeEvent ruleChangeEvent = new RuleSetChangeEvent(eventProjectKeys.toArray(String[]::new), activatedRules, deactivatedRules);
+ underTest.listen(ruleChangeEvent);
+
+ ArgumentCaptor<Set<String>> argument = ArgumentCaptor.forClass(Set.class);
+ verify(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), argument.capture());
+ assertThat(argument.getValue()).isEqualTo(clientProjectKeys);
+ }
+
+ @Test
+ public void listen_givenUserNotPermittedToReceiveEvent_closeConnection() {
+ RuleChange javaRuleChange = createRuleChange("java");
+ RuleChange[] activatedRules = {};
+ RuleChange[] deactivatedRules = {javaRuleChange};
+ RuleSetChangeEvent ruleChangeEvent = new RuleSetChangeEvent(exampleKeys.toArray(String[]::new), activatedRules, deactivatedRules);
+
+ SonarLintClient sonarLintClient = createSampleSLClient();
+ underTest.registerClient(sonarLintClient);
+ doThrow(new ForbiddenException("Access forbidden")).when(permissionsValidator).validateUserCanReceivePushEventForProjects(anyString(), anySet());
+
+ underTest.listen(ruleChangeEvent);
+
+ verify(sonarLintClient).close();
+ }
+
+ private SonarLintClient createSampleSLClient() {
+ SonarLintClient mock = mock(SonarLintClient.class);
+ when(mock.getLanguages()).thenReturn(Set.of("java"));
+ when(mock.getClientProjectKeys()).thenReturn(exampleKeys);
+ when(mock.getUserUuid()).thenReturn("userUuid");
+ return mock;
+ }
+
private RuleChange createRuleChange(String language) {
RuleChange javaRule = new RuleChange();
javaRule.setLanguage(language);
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java
index d1ecab10fda..b6597478b60 100644
--- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/sonarlint/SonarLintPushActionTest.java
@@ -46,8 +46,9 @@ public class SonarLintPushActionTest {
private final UserSession userSession = mock(UserSession.class);
private final DbClient dbClient = mock(DbClient.class);
private final ProjectDao projectDao = mock(ProjectDao.class);
+ private final SonarLintClientPermissionsValidator permissionsValidator = mock(SonarLintClientPermissionsValidator.class);
- private final WsPushActionTester ws = new WsPushActionTester(new SonarLintPushAction(registry, userSession, dbClient));
+ private final WsPushActionTester ws = new WsPushActionTester(new SonarLintPushAction(registry, userSession, dbClient, permissionsValidator));
@Before
public void before() {