From 8ec44b87f3f3bd2450009775abd65d2a75c4e791 Mon Sep 17 00:00:00 2001 From: Pierre Date: Wed, 17 Nov 2021 16:11:26 +0100 Subject: [PATCH] SONAR-13426 Validate token for public project when auth is forced --- .../UserSessionInitializer.java | 1 - .../server/badge/ws/ProjectBadgesSupport.java | 12 ++- .../server/badge/ws/MeasureActionTest.java | 70 ++++++++++++++++- .../badge/ws/QualityGateActionTest.java | 75 ++++++++++++++++++- 4 files changed, 147 insertions(+), 11 deletions(-) diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java index 5582633876a..fd82dca2318 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserSessionInitializer.java @@ -19,7 +19,6 @@ */ package org.sonar.server.authentication; -import com.google.common.collect.ImmutableSet; import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesSupport.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesSupport.java index 405cdef4dca..b803c2ab4a0 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesSupport.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/badge/ws/ProjectBadgesSupport.java @@ -20,6 +20,7 @@ package org.sonar.server.badge.ws; import javax.annotation.Nullable; +import org.sonar.api.config.Configuration; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.db.DbClient; @@ -30,6 +31,8 @@ import org.sonar.db.project.ProjectDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.NotFoundException; +import static org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE; +import static org.sonar.api.CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY; import static org.sonar.db.component.BranchType.BRANCH; import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; @@ -44,10 +47,12 @@ public class ProjectBadgesSupport { private final ComponentFinder componentFinder; private final DbClient dbClient; + private final Configuration config; - public ProjectBadgesSupport(ComponentFinder componentFinder, DbClient dbClient) { + public ProjectBadgesSupport(ComponentFinder componentFinder, DbClient dbClient, Configuration config) { this.componentFinder = componentFinder; this.dbClient = dbClient; + this.config = config; } void addProjectAndBranchParams(WebService.NewAction action) { @@ -101,8 +106,9 @@ public class ProjectBadgesSupport { } catch (NotFoundException e) { throw new NotFoundException(PROJECT_HAS_NOT_BEEN_FOUND); } - String token = request.param(PARAM_TOKEN); - if (projectDto.isPrivate() && !isTokenValid(dbSession, projectDto, token)) { + boolean tokenInvalid = !isTokenValid(dbSession, projectDto, request.param(PARAM_TOKEN)); + boolean forceAuthEnabled = config.getBoolean(CORE_FORCE_AUTHENTICATION_PROPERTY).orElse(CORE_FORCE_AUTHENTICATION_DEFAULT_VALUE); + if ((projectDto.isPrivate() || forceAuthEnabled) && tokenInvalid) { throw new NotFoundException(PROJECT_HAS_NOT_BEEN_FOUND); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java index e319a912e71..c94cc4258cc 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java @@ -26,20 +26,25 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Configuration; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric.Level; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; +import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbTester; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import org.sonar.db.metric.MetricDto; +import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.badge.ws.SvgGenerator.Color; import org.sonar.server.component.ComponentFinder; @@ -79,14 +84,20 @@ public class MeasureActionTest { @Rule public DbTester db = DbTester.create(); - private MapSettings mapSettings = new MapSettings().setProperty("sonar.sonarcloud.enabled", false); + private final MapSettings mapSettings = new MapSettings().setProperty("sonar.sonarcloud.enabled", false); + private final Configuration config = mapSettings.asConfig(); - private WsActionTester ws = new WsActionTester( + private final WsActionTester ws = new WsActionTester( new MeasureAction( db.getDbClient(), - new ProjectBadgesSupport(new ComponentFinder(db.getDbClient(), null), db.getDbClient()), + new ProjectBadgesSupport(new ComponentFinder(db.getDbClient(), null), db.getDbClient(), config), new SvgGenerator(mapSettings.asConfig()))); + @Before + public void before(){ + mapSettings.setProperty(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, false); + } + @Test public void int_measure() { ComponentDto project = db.components().insertPublicProject(); @@ -318,7 +329,7 @@ public class MeasureActionTest { } @Test - public void return_error_on_private_project() throws ParseException { + public void return_error_on_private_project_without_token() throws ParseException { ComponentDto project = db.components().insertPrivateProject(); UserDto user = db.users().insertUser(); userSession.logIn(user).addProjectPermission(USER, project); @@ -332,6 +343,57 @@ public class MeasureActionTest { checkError(response, "Project has not been found"); } + @DataProvider + public static Object[][] publicProject_forceAuth_accessGranted(){ + return new Object[][] { + // public project, force auth : works depending on token's validity + {true, true, true, true}, + {true, true, false, false}, + + // public project, no force auth : access always granted + {true, false, true, true}, + {true, false, false, true}, + + // private project, regardless of force auth, access granted depending on token's validity: + {false, true, true, true}, + {false, true, false, false}, + {false, false, true, true}, + {false, false, false, false}, + }; + } + + @Test + @UseDataProvider("publicProject_forceAuth_accessGranted") + public void badge_accessible_on_private_project_with_token(boolean publicProject, boolean forceAuth, + boolean validToken, boolean accessGranted) throws ParseException { + ComponentDto projectAsComponent = publicProject ? db.components().insertPublicProject() : db.components().insertPrivateProject(); + userSession.registerComponents(projectAsComponent); + MetricDto metric = db.measures().insertMetric(m -> m.setKey(BUGS_KEY).setValueType(INT.name())); + + db.measures().insertLiveMeasure(projectAsComponent, metric, m -> m.setValue(10_000d)); + ProjectDto project = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), projectAsComponent.getKey()) + .orElseThrow(() -> new IllegalStateException("project not found")); + + String token = db.getDbClient().projectBadgeTokenDao() + .insert(db.getSession(), UuidFactoryFast.getInstance().create(), project, "user-uuid", "user-login") + .getToken(); + db.commit(); + + mapSettings.setProperty(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, forceAuth); + + TestResponse response = ws.newRequest() + .setParam("project", projectAsComponent.getKey()) + .setParam("metric", metric.getKey()) + .setParam("token", validToken ? token : "invalid-token") + .execute(); + + if(accessGranted){ + checkSvg(response, "bugs", "10k", DEFAULT); + }else{ + checkError(response, "Project has not been found"); + } + } + @Test public void return_error_on_provisioned_project() throws ParseException { ComponentDto project = db.components().insertPublicProject(); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java index 29fc9c9d4d2..de3c268b23a 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java @@ -19,23 +19,32 @@ */ package org.sonar.server.badge.ws; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Locale; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Configuration; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.measures.Metric.Level; import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService.Param; +import org.sonar.core.util.UuidFactoryFast; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import org.sonar.db.measure.LiveMeasureDto; import org.sonar.db.metric.MetricDto; +import org.sonar.db.project.ProjectDto; import org.sonar.db.user.UserDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.tester.UserSessionRule; @@ -51,6 +60,7 @@ import static org.sonar.api.measures.Metric.ValueType.LEVEL; import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.component.BranchType.BRANCH; +@RunWith(DataProviderRunner.class) public class QualityGateActionTest { @Rule @@ -60,13 +70,20 @@ public class QualityGateActionTest { @Rule public DbTester db = DbTester.create(); - private final MapSettings mapSettings = new MapSettings().setProperty("sonar.sonarcloud.enabled", false); + private final MapSettings mapSettings = new MapSettings().setProperty("sonar.sonarcloud.enabled", false).setProperty(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, false); + private final Configuration config = mapSettings.asConfig(); - private WsActionTester ws = new WsActionTester( + private final WsActionTester ws = new WsActionTester( new QualityGateAction(db.getDbClient(), - new ProjectBadgesSupport(new ComponentFinder(db.getDbClient(), null), db.getDbClient()), + new ProjectBadgesSupport(new ComponentFinder(db.getDbClient(), null), db.getDbClient(), config), new SvgGenerator(mapSettings.asConfig()))); + + @Before + public void before(){ + mapSettings.setProperty(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, false); + } + @Test public void quality_gate_passed() { ComponentDto project = db.components().insertPublicProject(); @@ -95,6 +112,58 @@ public class QualityGateActionTest { checkResponse(response, ERROR); } + + @DataProvider + public static Object[][] publicProject_forceAuth_validToken_accessGranted(){ + return new Object[][] { + // public project, force auth : access granted depending on token's validity + {true, true, true, true}, + {true, true, false, false}, + + // public project, no force auth : access always granted + {true, false, true, true}, + {true, false, false, true}, + + // private project, regardless of force auth, access granted depending on token's validity: + {false, true, true, true}, + {false, true, false, false}, + {false, false, true, true}, + {false, false, false, false}, + }; + } + + @Test + @UseDataProvider("publicProject_forceAuth_validToken_accessGranted") + public void badge_accessible_on_private_project_with_token(boolean publicProject, boolean forceAuth, + boolean validToken, boolean accessGranted) throws ParseException { + ComponentDto projectAsComponent = publicProject ? db.components().insertPublicProject() : db.components().insertPrivateProject(); + userSession.registerComponents(projectAsComponent); + MetricDto metric = createQualityGateMetric(); + + db.measures().insertLiveMeasure(projectAsComponent, metric, m -> m.setData(OK.name())); + ProjectDto project = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), projectAsComponent.getKey()) + .orElseThrow(() -> new IllegalStateException("project not found")); + + String token = db.getDbClient().projectBadgeTokenDao() + .insert(db.getSession(), UuidFactoryFast.getInstance().create(), project, "user-uuid", "user-login") + .getToken(); + db.commit(); + + mapSettings.setProperty(CoreProperties.CORE_FORCE_AUTHENTICATION_PROPERTY, forceAuth); + + TestResponse response = ws.newRequest() + .setParam("project", projectAsComponent.getKey()) + .setParam("token", validToken ? token : "invalid-token") + .execute(); + + if(accessGranted){ + checkResponse(response, OK); + }else{ + checkError(response, "Project has not been found"); + } + + } + @Test public void etag_should_be_different_if_quality_gate_is_different() { ComponentDto project = db.components().insertPublicProject(); -- 2.39.5