Skip to content

Commit

Permalink
SLCORE-818 check SC url from origin request header and check from Son…
Browse files Browse the repository at this point in the history
…arCloudActiveEnvironment
  • Loading branch information
serhat-yenican-sonarsource committed May 29, 2024
1 parent e0ab5b0 commit 55b6d06
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIBuilder;
import org.sonarsource.sonarlint.core.ServerApiProvider;
import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
import org.sonarsource.sonarlint.core.file.FilePathTranslation;
import org.sonarsource.sonarlint.core.file.PathTranslationService;
Expand All @@ -59,7 +62,6 @@

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment.PRODUCTION_URI;

@Named
@Singleton
Expand All @@ -70,14 +72,16 @@ public class ShowIssueRequestHandler implements HttpRequestHandler {
private final TelemetryService telemetryService;
private final RequestHandlerBindingAssistant requestHandlerBindingAssistant;
private final PathTranslationService pathTranslationService;
private final SonarCloudActiveEnvironment sonarCloudActiveEnvironment;

public ShowIssueRequestHandler(SonarLintRpcClient client, ServerApiProvider serverApiProvider, TelemetryService telemetryService,
RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService) {
RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService, SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {
this.client = client;
this.serverApiProvider = serverApiProvider;
this.telemetryService = telemetryService;
this.requestHandlerBindingAssistant = requestHandlerBindingAssistant;
this.pathTranslationService = pathTranslationService;
this.sonarCloudActiveEnvironment = sonarCloudActiveEnvironment;
}

@Override
Expand Down Expand Up @@ -106,6 +110,13 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Htt
response.setEntity(new StringEntity("OK"));
}

private boolean isSonarCloud(ClassicHttpRequest request) throws ProtocolException {
return Optional.ofNullable(request.getHeader("Origin"))
.map(NameValuePair::getValue)
.map(serverUrl -> sonarCloudActiveEnvironment.getUri().toString().equals(serverUrl))
.orElse(false);
}

private void showIssueForScope(String connectionId, String configScopeId, String issueKey, String projectKey,
String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) {
var issueDetailsOpt = tryFetchIssue(connectionId, issueKey, projectKey, branch, pullRequest, cancelMonitor);
Expand Down Expand Up @@ -165,7 +176,7 @@ private Optional<String> tryFetchCodeSnippet(String connectionId, String fileKey
}

@VisibleForTesting
static ShowIssueQuery extractQuery(ClassicHttpRequest request) {
ShowIssueQuery extractQuery(ClassicHttpRequest request) throws ProtocolException {
var params = new HashMap<String, String>();
try {
new URIBuilder(request.getUri(), StandardCharsets.UTF_8)
Expand All @@ -174,8 +185,10 @@ static ShowIssueQuery extractQuery(ClassicHttpRequest request) {
} catch (URISyntaxException e) {
// Ignored
}
return new ShowIssueQuery(params.get("server"), params.get("project"), params.get("issue"), params.get("branch"),
params.get("pullRequest"), params.get("tokenName"), params.get("tokenValue"), params.get("organizationKey"));
boolean isSonarCloud = isSonarCloud(request);
String serverUrl = isSonarCloud ? sonarCloudActiveEnvironment.getUri().toString() : params.get("server");
return new ShowIssueQuery(serverUrl, params.get("project"), params.get("issue"), params.get("branch"),
params.get("pullRequest"), params.get("tokenName"), params.get("tokenValue"), params.get("organizationKey"), isSonarCloud);
}

@VisibleForTesting
Expand All @@ -192,9 +205,10 @@ public static class ShowIssueQuery {
private final String tokenValue;
@Nullable
private final String organizationKey;
private final boolean isSonarCloud;

public ShowIssueQuery(String serverUrl, String projectKey, String issueKey, String branch,
@Nullable String pullRequest, @Nullable String tokenName, @Nullable String tokenValue, @Nullable String organizationKey) {
@Nullable String pullRequest, @Nullable String tokenName, @Nullable String tokenValue, @Nullable String organizationKey, boolean isSonarCloud) {
this.serverUrl = serverUrl;
this.projectKey = projectKey;
this.issueKey = issueKey;
Expand All @@ -203,18 +217,16 @@ public ShowIssueQuery(String serverUrl, String projectKey, String issueKey, Stri
this.tokenName = tokenName;
this.tokenValue = tokenValue;
this.organizationKey = organizationKey;
this.isSonarCloud = isSonarCloud;
}

public boolean isValid() {
return isNotBlank(serverUrl) && isNotBlank(projectKey) && isNotBlank(issueKey) && isNotBlank(branch)
&& (!isSonarCloud(serverUrl) || isNotBlank(organizationKey))
return isNotBlank(projectKey) && isNotBlank(issueKey) && isNotBlank(branch)
&& (isSonarCloud || isNotBlank(serverUrl))
&& (!isSonarCloud || isNotBlank(organizationKey))
&& isPullRequestParamValid() && isTokenValid();
}

private static boolean isSonarCloud(String serverUrl) {
return PRODUCTION_URI.toString().equals(serverUrl);
}

public boolean isPullRequestParamValid() {
if (pullRequest != null) {
return isNotEmpty(pullRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,14 @@
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.junit.jupiter.api.Test;
import org.sonarsource.sonarlint.core.BindingCandidatesFinder;
import org.sonarsource.sonarlint.core.BindingSuggestionProvider;
import org.sonarsource.sonarlint.core.ServerApiProvider;
import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
import org.sonarsource.sonarlint.core.file.FilePathTranslation;
import org.sonarsource.sonarlint.core.file.PathTranslationService;
Expand Down Expand Up @@ -148,8 +150,10 @@ void should_trigger_telemetry() throws HttpException, IOException, URISyntaxExce
}

@Test
void should_extract_query_from_request_without_token() {
var issueQuery = ShowIssueRequestHandler.extractQuery(new BasicClassicHttpRequest("GET", "/sonarlint/api/issues/show" +
void should_extract_query_from_sq_request_without_token() throws ProtocolException {
SonarCloudActiveEnvironment sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();
ShowIssueRequestHandler showIssueRequestHandler = new ShowIssueRequestHandler(null, null, null, null, null, sonarCloudActiveEnvironment);
var issueQuery = showIssueRequestHandler.extractQuery(new BasicClassicHttpRequest("GET", "/sonarlint/api/issues/show" +
"?server=https%3A%2F%2Fnext.sonarqube.com%2Fsonarqube" +
"&project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent" +
"&issue=AX2VL6pgAvx3iwyNtLyr" +
Expand All @@ -163,8 +167,10 @@ void should_extract_query_from_request_without_token() {
}

@Test
void should_extract_query_from_request_with_token() {
var issueQuery = ShowIssueRequestHandler.extractQuery(new BasicClassicHttpRequest("GET", "/sonarlint/api/issues/show" +
void should_extract_query_from_sq_request_with_token() throws ProtocolException {
SonarCloudActiveEnvironment sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();
ShowIssueRequestHandler showIssueRequestHandler = new ShowIssueRequestHandler(null, null, null, null, null, sonarCloudActiveEnvironment);
var issueQuery = showIssueRequestHandler.extractQuery(new BasicClassicHttpRequest("GET", "/sonarlint/api/issues/show" +
"?server=https%3A%2F%2Fnext.sonarqube.com%2Fsonarqube" +
"&project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent" +
"&issue=AX2VL6pgAvx3iwyNtLyr&tokenName=abc" +
Expand All @@ -179,27 +185,63 @@ void should_extract_query_from_request_with_token() {
}

@Test
void should_validate_issue_query() {
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", null, null, null).isValid()).isTrue();

assertThat(new ShowIssueRequestHandler.ShowIssueQuery("", "project", "issue", "branch", "pullRequest", null, null, null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "", "issue", "branch", "pullRequest", null, null, null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "", "branch", "pullRequest", null, null, null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "", "", null, null, null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", null, null, null, null).isValid()).isTrue();

assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "name", null, null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", null, "value", null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "name", "", null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "", "value", null).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "name", "value", null).isValid()).isTrue();

assertThat(new ShowIssueRequestHandler.ShowIssueQuery("https://sonarcloud.io", "project", "issue", "branch", "pullRequest", "name", "value", "organizationKey").isValid()).isTrue();
void should_extract_query_from_sc_request_without_token() throws ProtocolException {
SonarCloudActiveEnvironment sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();
ShowIssueRequestHandler showIssueRequestHandler = new ShowIssueRequestHandler(null, null, null, null, null, sonarCloudActiveEnvironment);
BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/sonarlint/api/issues/show" +
"?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent" +
"&issue=AX2VL6pgAvx3iwyNtLyr" +
"&organizationKey=sample-organization");
request.addHeader("Origin", SonarCloudActiveEnvironment.PRODUCTION_URI);
var issueQuery = showIssueRequestHandler.extractQuery(request);
assertThat(issueQuery.getServerUrl()).isEqualTo("https://sonarcloud.io");
assertThat(issueQuery.getProjectKey()).isEqualTo("org.sonarsource.sonarlint.core:sonarlint-core-parent");
assertThat(issueQuery.getIssueKey()).isEqualTo("AX2VL6pgAvx3iwyNtLyr");
assertThat(issueQuery.getOrganizationKey()).isEqualTo("sample-organization");
assertThat(issueQuery.getTokenName()).isNull();
assertThat(issueQuery.getTokenValue()).isNull();
}

@Test
void should_extract_query_from_sc_request_with_token() throws ProtocolException {
SonarCloudActiveEnvironment sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();
ShowIssueRequestHandler showIssueRequestHandler = new ShowIssueRequestHandler(null, null, null, null, null, sonarCloudActiveEnvironment);
BasicClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/sonarlint/api/issues/show" +
"?project=org.sonarsource.sonarlint.core%3Asonarlint-core-parent" +
"&issue=AX2VL6pgAvx3iwyNtLyr&tokenName=abc" +
"&organizationKey=sample-organization" +
"&tokenValue=123");
request.addHeader("Origin", SonarCloudActiveEnvironment.PRODUCTION_URI);
var issueQuery = showIssueRequestHandler.extractQuery(request);
assertThat(issueQuery.getServerUrl()).isEqualTo("https://sonarcloud.io");
assertThat(issueQuery.getProjectKey()).isEqualTo("org.sonarsource.sonarlint.core:sonarlint-core-parent");
assertThat(issueQuery.getIssueKey()).isEqualTo("AX2VL6pgAvx3iwyNtLyr");
assertThat(issueQuery.getTokenName()).isEqualTo("abc");
assertThat(issueQuery.getOrganizationKey()).isEqualTo("sample-organization");
assertThat(issueQuery.getTokenValue()).isEqualTo("123");
}

@Test
void should_validate_issue_query_for_sq() {
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", null, null, null, false).isValid()).isTrue();

assertThat(new ShowIssueRequestHandler.ShowIssueQuery("", "project", "issue", "branch", "pullRequest", null, null, null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "", "issue", "branch", "pullRequest", null, null, null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "", "branch", "pullRequest", null, null, null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "", "", null, null, null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", null, null, null, null, false).isValid()).isTrue();

assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "name", null, null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", null, "value", null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "name", "", null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "", "value", null, false).isValid()).isFalse();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("serverUrl", "project", "issue", "branch", "pullRequest", "name", "value", null, false).isValid()).isTrue();
}

@Test
void should_invalid_when_no_organization_key_for_SC() {
assertThat(new ShowIssueRequestHandler.ShowIssueQuery("https://sonarcloud.io", "project", "issue", "branch", "pullRequest", "name", "value", null).isValid()).isFalse();
void should_validate_issue_query_for_sc() {
assertThat(new ShowIssueRequestHandler.ShowIssueQuery(null, "project", "issue", "branch", "pullRequest", "name", "value", "organizationKey", true).isValid()).isTrue();
assertThat(new ShowIssueRequestHandler.ShowIssueQuery(null, "project", "issue", "branch", "pullRequest", "name", "value", null, true).isValid()).isFalse();
}

@Test
Expand Down Expand Up @@ -228,7 +270,8 @@ private static ShowIssueRequestHandler getShowIssueRequestHandler(TelemetryServi
var sonarLintClient = mock(SonarLintRpcClient.class);
var pathTranslationService = mock(PathTranslationService.class);
var userTokenService = mock(UserTokenService.class);
SonarCloudActiveEnvironment sonarCloudActiveEnvironment = SonarCloudActiveEnvironment.prod();
return new ShowIssueRequestHandler(sonarLintClient, serverApiProvider, telemetryService,
new RequestHandlerBindingAssistant(bindingSuggestionProvider, bindingCandidatesFinder, sonarLintClient, repository, configurationRepository, userTokenService), pathTranslationService);
new RequestHandlerBindingAssistant(bindingSuggestionProvider, bindingCandidatesFinder, sonarLintClient, repository, configurationRepository, userTokenService), pathTranslationService, sonarCloudActiveEnvironment);
}
}

0 comments on commit 55b6d06

Please sign in to comment.