Skip to content

Commit

Permalink
SLCORE-1079 Fix SSF-694
Browse files Browse the repository at this point in the history
  • Loading branch information
nquinquenel committed Dec 6, 2024
1 parent 545cf53 commit 60455ce
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider;
import org.sonarsource.sonarlint.core.http.HttpClient;
import org.sonarsource.sonarlint.core.http.HttpClientProvider;
Expand All @@ -40,6 +41,7 @@
import org.sonarsource.sonarlint.core.serverapi.EndpointParams;
import org.sonarsource.sonarlint.core.serverapi.ServerApi;
import org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;
import org.sonarsource.sonarlint.core.serverconnection.ServerVersionAndStatusChecker;

import static org.apache.commons.lang.StringUtils.removeEnd;

Expand Down Expand Up @@ -67,12 +69,22 @@ public Optional<ServerApi> getServerApi(String connectionId) {
LOG.debug("Connection '{}' is gone", connectionId);
return Optional.empty();
}
return Optional.of(new ServerApi(params.get(), awareHttpClientProvider.getHttpClient(connectionId)));
var isBearerSupported = checkIfBearerIsSupported(params.get());
return Optional.of(new ServerApi(params.get(), awareHttpClientProvider.getHttpClient(connectionId, isBearerSupported)));
}

private boolean checkIfBearerIsSupported(EndpointParams params) {
var httpClient = awareHttpClientProvider.getHttpClient();
var cancelMonitor = new SonarLintCancelMonitor();
var serverApi = new ServerApi(params, httpClient);
var status = serverApi.system().getStatus(cancelMonitor);
var serverChecker = new ServerVersionAndStatusChecker(serverApi);
return serverChecker.isSupportingBearer(status);
}

public ServerApi getServerApi(String baseUrl, @Nullable String organization, String token) {
var params = new EndpointParams(baseUrl, removeEnd(sonarCloudUri.toString(), "/").equals(removeEnd(baseUrl, "/")), organization);
return new ServerApi(params, httpClientProvider.getHttpClientWithPreemptiveAuth(token));
return new ServerApi(params, httpClientProvider.getHttpClientWithPreemptiveAuth(token, true));
}

public ServerApi getServerApiOrThrow(String connectionId) {
Expand All @@ -81,30 +93,34 @@ public ServerApi getServerApiOrThrow(String connectionId) {
var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, "Connection '" + connectionId + "' is gone", connectionId);
throw new ResponseErrorException(error);
}
return new ServerApi(params.get(), awareHttpClientProvider.getHttpClient(connectionId));
var isBearerSupported = checkIfBearerIsSupported(params.get());
return new ServerApi(params.get(), awareHttpClientProvider.getHttpClient(connectionId, isBearerSupported));
}

/**
* Used to do SonarCloud requests before knowing the organization
*/
public ServerApi getForSonarCloudNoOrg(Either<TokenDto, UsernamePasswordDto> credentials) {
var endpointParams = new EndpointParams(sonarCloudUri.toString(), true, null);
var httpClient = getClientFor(credentials);
var httpClient = getClientFor(endpointParams, credentials);
return new ServerApi(new ServerApiHelper(endpointParams, httpClient));
}

public ServerApi getForTransientConnection(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection) {
var endpointParams = transientConnection.map(
sq -> new EndpointParams(sq.getServerUrl(), false, null),
sc -> new EndpointParams(sonarCloudUri.toString(), true, sc.getOrganization()));
var httpClient = getClientFor(transientConnection
var httpClient = getClientFor(endpointParams, transientConnection
.map(TransientSonarQubeConnectionDto::getCredentials, TransientSonarCloudConnectionDto::getCredentials));
return new ServerApi(new ServerApiHelper(endpointParams, httpClient));
}

private HttpClient getClientFor(Either<TokenDto, UsernamePasswordDto> credentials) {
private HttpClient getClientFor(EndpointParams params, Either<TokenDto, UsernamePasswordDto> credentials) {
return credentials.map(
tokenDto -> httpClientProvider.getHttpClientWithPreemptiveAuth(tokenDto.getToken()),
tokenDto -> {
var isBearerSupported = checkIfBearerIsSupported(params);
return httpClientProvider.getHttpClientWithPreemptiveAuth(tokenDto.getToken(), isBearerSupported);
},
userPass -> httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ public HttpClient getHttpClient() {
return httpClientProvider.getHttpClient();
}

public HttpClient getHttpClient(String connectionId) {
public HttpClient getHttpClient(String connectionId, boolean shouldUseBearer) {
var credentials = queryClientForConnectionCredentials(connectionId);
if (credentials.isEmpty()) {
// Fallback on client with no authentication
return httpClientProvider.getHttpClient();
}
return credentials.get().map(
tokenDto -> httpClientProvider.getHttpClientWithPreemptiveAuth(tokenDto.getToken()),
tokenDto -> httpClientProvider.getHttpClientWithPreemptiveAuth(tokenDto.getToken(), shouldUseBearer),
userPass -> httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void getServerApi_for_sonarqube() {
var endpointParams = mock(EndpointParams.class);
when(connectionRepository.getEndpointParams("sq1")).thenReturn(Optional.of(endpointParams));
var httpClient = mock(HttpClient.class);
when(awareHttpClientProvider.getHttpClient("sq1")).thenReturn(httpClient);
when(awareHttpClientProvider.getHttpClient("sq1", false)).thenReturn(httpClient);

var serverApi = underTest.getServerApi("sq1");

Expand All @@ -59,7 +59,7 @@ void getServerApi_for_sonarqube() {
@Test
void getServerApi_for_sonarqube_notConnected() {
var httpClient = mock(HttpClient.class);
when(httpClientProvider.getHttpClientWithPreemptiveAuth("token")).thenReturn(httpClient);
when(httpClientProvider.getHttpClientWithPreemptiveAuth("token", false)).thenReturn(httpClient);

var serverApi = underTest.getServerApi("sq_notConnected", null, "token");
assertThat(serverApi.isSonarCloud()).isFalse();
Expand All @@ -70,7 +70,7 @@ void getServerApi_for_sonarcloud() {
var endpointParams = mock(EndpointParams.class);
when(connectionRepository.getEndpointParams("sc1")).thenReturn(Optional.of(endpointParams));
var httpClient = mock(HttpClient.class);
when(awareHttpClientProvider.getHttpClient("sc1")).thenReturn(httpClient);
when(awareHttpClientProvider.getHttpClient("sc1", true)).thenReturn(httpClient);

var serverApi = underTest.getServerApi("sc1");

Expand All @@ -80,7 +80,7 @@ void getServerApi_for_sonarcloud() {
@Test
void getServerApi_for_sonarcloud_with_trailing_slash_notConnected() {
var httpClient = mock(HttpClient.class);
when(httpClientProvider.getHttpClientWithPreemptiveAuth("token")).thenReturn(httpClient);
when(httpClientProvider.getHttpClientWithPreemptiveAuth("token", true)).thenReturn(httpClient);

var serverApi = underTest.getServerApi("https://sonarcloud.io/", "organization", "token");
assertThat(serverApi.isSonarCloud()).isTrue();
Expand All @@ -89,7 +89,7 @@ void getServerApi_for_sonarcloud_with_trailing_slash_notConnected() {
@Test
void getServerApi_for_sonarcloud_notConnected() {
var httpClient = mock(HttpClient.class);
when(httpClientProvider.getHttpClientWithPreemptiveAuth("token")).thenReturn(httpClient);
when(httpClientProvider.getHttpClientWithPreemptiveAuth("token", true)).thenReturn(httpClient);

var serverApi = underTest.getServerApi("https://sonarcloud.io", "organization", "token");
assertThat(serverApi.isSonarCloud()).isTrue();
Expand All @@ -99,7 +99,7 @@ void getServerApi_for_sonarcloud_notConnected() {
void getServerApi_returns_empty_if_connection_doesnt_exists() {
when(connectionRepository.getConnectionById("sc1")).thenReturn(null);
var httpClient = mock(HttpClient.class);
when(awareHttpClientProvider.getHttpClient("sc1")).thenReturn(httpClient);
when(awareHttpClientProvider.getHttpClient("sc1", true)).thenReturn(httpClient);

var serverApi = underTest.getServerApi("sc1");

Expand All @@ -109,7 +109,7 @@ void getServerApi_returns_empty_if_connection_doesnt_exists() {
@Test
void getServerApi_returns_empty_if_client_cant_provide_httpclient() {
when(connectionRepository.getConnectionById("sc1")).thenReturn(new SonarCloudConnectionConfiguration(URI.create("http://server1"), "sc1", "myorg", true));
when(awareHttpClientProvider.getHttpClient("sc1")).thenReturn(null);
when(awareHttpClientProvider.getHttpClient("sc1", true)).thenReturn(null);

var serverApi = underTest.getServerApi("sc1");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,22 @@
class ApacheHttpClientAdapter implements HttpClient {

private static final SonarLintLogger LOG = SonarLintLogger.get();

private static final String AUTHORIZATION_HEADER = "Authorization";
private static final Timeout STREAM_CONNECTION_REQUEST_TIMEOUT = Timeout.ofSeconds(10);
private static final Timeout STREAM_CONNECTION_TIMEOUT = Timeout.ofMinutes(1);
private final CloseableHttpAsyncClient apacheClient;
@Nullable
private final String usernameOrToken;
@Nullable
private final String password;
private final boolean shouldUseBearer;
private boolean connected = false;

ApacheHttpClientAdapter(CloseableHttpAsyncClient apacheClient, @Nullable String usernameOrToken, @Nullable String password) {
private ApacheHttpClientAdapter(CloseableHttpAsyncClient apacheClient, @Nullable String usernameOrToken, @Nullable String password, boolean shouldUseBearer) {
this.apacheClient = apacheClient;
this.usernameOrToken = usernameOrToken;
this.password = password;
this.shouldUseBearer = shouldUseBearer;
}

@Override
Expand Down Expand Up @@ -109,7 +111,11 @@ public AsyncRequest getEventStream(String url, HttpConnectionListener connection
.build());

if (usernameOrToken != null) {
request.setHeader("Authorization", basic(usernameOrToken, Objects.requireNonNullElse(password, "")));
if (shouldUseBearer) {
request.setHeader(AUTHORIZATION_HEADER, bearer(usernameOrToken));
} else {
request.setHeader(AUTHORIZATION_HEADER, basic(usernameOrToken, Objects.requireNonNullElse(password, "")));
}
}
request.setHeader("Accept", "text/event-stream");
connected = false;
Expand Down Expand Up @@ -246,7 +252,11 @@ public boolean cancel(boolean mayInterruptIfRunning) {
private CompletableFuture<Response> executeAsync(SimpleHttpRequest httpRequest) {
try {
if (usernameOrToken != null) {
httpRequest.setHeader("Authorization", basic(usernameOrToken, Objects.requireNonNullElse(password, "")));
if (shouldUseBearer) {
httpRequest.setHeader(AUTHORIZATION_HEADER, bearer(usernameOrToken));
} else {
httpRequest.setHeader(AUTHORIZATION_HEADER, basic(usernameOrToken, Objects.requireNonNullElse(password, "")));
}
}
return new CompletableFutureWrappingFuture(httpRequest);
} catch (Exception e) {
Expand All @@ -260,6 +270,10 @@ private static String basic(String username, String password) {
return String.format("Basic %s", encoded);
}

private static String bearer(String token) {
return String.format("Bearer %s", token);
}

public static class HttpAsyncRequest implements AsyncRequest {
private final Future<?> response;

Expand All @@ -275,7 +289,18 @@ public void cancel() {
// ignore errors
}
}
}

public static ApacheHttpClientAdapter withoutCredentials(CloseableHttpAsyncClient apacheClient) {
return new ApacheHttpClientAdapter(apacheClient, null, null, false);
}

public static ApacheHttpClientAdapter withUsernamePassword(CloseableHttpAsyncClient apacheClient, String username, @Nullable String password) {
return new ApacheHttpClientAdapter(apacheClient, username, password, false);
}

public static ApacheHttpClientAdapter withToken(CloseableHttpAsyncClient apacheClient, String token, boolean shouldUseBearer) {
return new ApacheHttpClientAdapter(apacheClient, token, null, shouldUseBearer);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,15 @@ private static RequestConfig buildRequestConfig(@Nullable Timeout connectionRequ
}

public HttpClient getHttpClient() {
return new ApacheHttpClientAdapter(sharedClient, null, null);
return ApacheHttpClientAdapter.withoutCredentials(sharedClient);
}

public HttpClient getHttpClientWithPreemptiveAuth(String username, @Nullable String password) {
return new ApacheHttpClientAdapter(sharedClient, username, password);
return ApacheHttpClientAdapter.withUsernamePassword(sharedClient, username, password);
}

public HttpClient getHttpClientWithPreemptiveAuth(String token) {
return new ApacheHttpClientAdapter(sharedClient, token, null);
public HttpClient getHttpClientWithPreemptiveAuth(String token, boolean shouldUseBearer) {
return ApacheHttpClientAdapter.withToken(sharedClient, token, shouldUseBearer);
}

public WebSocketClient getWebSocketClient(String token) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,6 @@ public int getEmbeddedServerPort() {
return getInitializedApplicationContext().getBean(EmbeddedServer.class).getPort();
}

public HttpClient getHttpClientNoAuth() {
return getInitializedApplicationContext().getBean(ConnectionAwareHttpClientProvider.class).getHttpClient();
}

public HttpClient getHttpClient(String connectionId) {
return getInitializedApplicationContext().getBean(ConnectionAwareHttpClientProvider.class).getHttpClient(connectionId);
}

public LocalOnlyIssueStorageService getLocalOnlyIssueStorageService() {
return getInitializedApplicationContext().getBean(LocalOnlyIssueStorageService.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
public class ServerVersionAndStatusChecker {

private static final String MIN_SQ_VERSION = "9.9";
private static final String MIN_SQ_VERSION_SUPPORTING_BEARER = "10.0";
private final SystemApi systemApi;
private final boolean isSonarCloud;

Expand All @@ -53,6 +54,15 @@ public void checkVersionAndStatus(SonarLintCancelMonitor cancelMonitor) {
}
}

public boolean isSupportingBearer(ServerStatusInfo serverStatus) {
if (isSonarCloud) {
return true;
} else {
var serverVersion = Version.create(serverStatus.getVersion());
return serverVersion.compareToIgnoreQualifier(Version.create(MIN_SQ_VERSION_SUPPORTING_BEARER)) >= 0;
}
}

private static void checkServerUp(ServerStatusInfo serverStatus) {
if (!serverStatus.isUp()) {
throw new IllegalStateException(serverNotReady(serverStatus));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,4 @@ public int getEmbeddedServerPort() {
return serverUsingJava.getEmbeddedServerPort();
}

@Deprecated(forRemoval = true)
public HttpClient getHttpClientNoAuth() {
return serverUsingJava.getHttpClientNoAuth();
}

@Deprecated(forRemoval = true)
public HttpClient getHttpClient(String connectionId) {
return serverUsingJava.getHttpClient(connectionId);
}

}

0 comments on commit 60455ce

Please sign in to comment.