Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLCORE-1032 Wrong token error during sync - [New design] #1187

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions API_CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Breaking changes

* Add new method `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#invalidToken` to notify client that WebAPI calls to SQS/SQC fails due to wrong token
* Client can implement this method to offer user to change credentials for the connection to fix the problem
* For now notification is being sent only for 403 Forbidden HTTP response code since it's corresponds to malformed/wrong token and ignores 401 Unauthorized response code since it's a user permissions problem that has to be addressed on the server
* Also once notification sent, backend doesn't attempt to send any requests to server anymore until credentials changed

* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#didRaiseIssue` and associated types. See `raiseIssues` and `raiseHotspots` instead.
* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getIssueTrackingService` and associated types. Tracking is managed by the backend.
* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getSecurityHotspotMatchingService` and associated types. Tracking is managed by the backend.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@

import java.net.URI;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.inject.Singleton;
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.connection.ServerConnection;
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;
import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;
Expand All @@ -47,19 +51,21 @@

@Named
@Singleton
public class ServerApiProvider {
public class ConnectionManager {

private static final SonarLintLogger LOG = SonarLintLogger.get();
private final ConnectionConfigurationRepository connectionRepository;
private final ConnectionAwareHttpClientProvider awareHttpClientProvider;
private final HttpClientProvider httpClientProvider;
private final SonarLintRpcClient client;
private final URI sonarCloudUri;

public ServerApiProvider(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider,
SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {
public ConnectionManager(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider,
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarLintRpcClient client) {
this.connectionRepository = connectionRepository;
this.awareHttpClientProvider = awareHttpClientProvider;
this.httpClientProvider = httpClientProvider;
this.client = client;
this.sonarCloudUri = sonarCloudActiveEnvironment.getUri();
}

Expand Down Expand Up @@ -100,7 +106,7 @@ public ServerApi getServerApi(String baseUrl, @Nullable String organization, Str
return new ServerApi(params, httpClientProvider.getHttpClientWithPreemptiveAuth(token, isBearerSupported));
}

public ServerApi getServerApiOrThrow(String connectionId) {
private ServerApi getServerApiOrThrow(String connectionId) {
var params = connectionRepository.getEndpointParams(connectionId);
if (params.isEmpty()) {
var error = new ResponseError(SonarLintRpcErrorCode.CONNECTION_NOT_FOUND, "Connection '" + connectionId + "' is gone", connectionId);
Expand Down Expand Up @@ -137,4 +143,51 @@ private HttpClient getClientFor(EndpointParams params, Either<TokenDto, Username
userPass -> httpClientProvider.getHttpClientWithPreemptiveAuth(userPass.getUsername(), userPass.getPassword()));
}

/**
* Throws ResponseErrorException if connection with provided ID is not found in ConnectionConfigurationRepository
*/
public ServerConnection getConnectionOrThrow(String connectionId) {
var serverApi = getServerApiOrThrow(connectionId);
return new ServerConnection(connectionId, serverApi, client);
}

/**
* Returns empty Optional if connection with provided ID is not found in ConnectionConfigurationRepository
*/
public Optional<ServerConnection> tryGetConnection(String connectionId) {
return getServerApi(connectionId)
.map(serverApi -> new ServerConnection(connectionId, serverApi, client));
}

/**
* Should be used for WebAPI requests without an authentication
*/
public Optional<ServerConnection> tryGetConnectionWithoutCredentials(String connectionId) {
return getServerApiWithoutCredentials(connectionId)
.map(serverApi -> new ServerConnection(connectionId, serverApi, client));
}

public ServerApi getTransientConnection(String token,@Nullable String organization, String baseUrl) {
return getServerApi(baseUrl, organization, token);
}

public void withValidConnection(String connectionId, Consumer<ServerApi> serverApiConsumer) {
getValidConnection(connectionId).ifPresent(connection -> connection.withClientApi(serverApiConsumer));
}

public <T> Optional<T> withValidConnectionAndReturn(String connectionId, Function<ServerApi, T> serverApiConsumer) {
return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer));
}

public <T> Optional<T> withValidConnectionFlatMapOptionalAndReturn(String connectionId, Function<ServerApi, Optional<T>> serverApiConsumer) {
return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)).flatMap(Function.identity());
}

private Optional<ServerConnection> getValidConnection(String connectionId) {
return tryGetConnection(connectionId).filter(ServerConnection::isValid)
.or(() -> {
LOG.debug("Connection '{}' is invalid", connectionId);
return Optional.empty();
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,23 @@ public class ConnectionService {
private final ApplicationEventPublisher applicationEventPublisher;
private final ConnectionConfigurationRepository repository;
private final URI sonarCloudUri;
private final ServerApiProvider serverApiProvider;
private final ConnectionManager connectionManager;
private final TokenGeneratorHelper tokenGeneratorHelper;

@Inject
public ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository, InitializeParams params,
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, ServerApiProvider serverApiProvider) {
this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, serverApiProvider,
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, ConnectionManager connectionManager) {
this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, connectionManager,
tokenGeneratorHelper);
}

ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository,
@Nullable List<SonarQubeConnectionConfigurationDto> initSonarQubeConnections, @Nullable List<SonarCloudConnectionConfigurationDto> initSonarCloudConnections,
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ServerApiProvider serverApiProvider, TokenGeneratorHelper tokenGeneratorHelper) {
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ConnectionManager connectionManager, TokenGeneratorHelper tokenGeneratorHelper) {
this.applicationEventPublisher = applicationEventPublisher;
this.repository = repository;
this.sonarCloudUri = sonarCloudActiveEnvironment.getUri();
this.serverApiProvider = serverApiProvider;
this.connectionManager = connectionManager;
this.tokenGeneratorHelper = tokenGeneratorHelper;
if (initSonarQubeConnections != null) {
initSonarQubeConnections.forEach(c -> repository.addOrReplace(adapt(c)));
Expand Down Expand Up @@ -157,7 +157,7 @@ private void updateConnection(AbstractConnectionConfiguration connectionConfigur

public ValidateConnectionResponse validateConnection(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,
SonarLintCancelMonitor cancelMonitor) {
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
var serverApi = connectionManager.getForTransientConnection(transientConnection);
var serverChecker = new ServerVersionAndStatusChecker(serverApi);
try {
serverChecker.checkVersionAndStatus(cancelMonitor);
Expand All @@ -179,7 +179,7 @@ public ValidateConnectionResponse validateConnection(Either<TransientSonarQubeCo

public boolean checkSmartNotificationsSupported(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,
SonarLintCancelMonitor cancelMonitor) {
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
var serverApi = connectionManager.getForTransientConnection(transientConnection);
var developersApi = serverApi.developers();
return developersApi.isSupported(cancelMonitor);
}
Expand All @@ -189,15 +189,15 @@ public HelpGenerateUserTokenResponse helpGenerateUserToken(String serverUrl, Son
}

public List<SonarProjectDto> getAllProjects(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection, SonarLintCancelMonitor cancelMonitor) {
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
var serverApi = connectionManager.getForTransientConnection(transientConnection);
return serverApi.component().getAllProjects(cancelMonitor)
.stream().map(serverProject -> new SonarProjectDto(serverProject.getKey(), serverProject.getName()))
.collect(Collectors.toList());
}

public Map<String, String> getProjectNamesByKey(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,
List<String> projectKeys, SonarLintCancelMonitor cancelMonitor) {
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
var serverApi = connectionManager.getForTransientConnection(transientConnection);
var projectNamesByKey = new HashMap<String, String>();
projectKeys.forEach(key -> {
var projectName = serverApi.component().getProject(key, cancelMonitor).map(ServerProject::getName).orElse(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@
public class OrganizationsCache {

private static final SonarLintLogger LOG = SonarLintLogger.get();
private final ServerApiProvider serverApiProvider;
private final ConnectionManager connectionManager;

private final Cache<Either<TokenDto, UsernamePasswordDto>, TextSearchIndex<OrganizationDto>> textSearchIndexCacheByCredentials = CacheBuilder.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();

public OrganizationsCache(ServerApiProvider serverApiProvider) {
this.serverApiProvider = serverApiProvider;
public OrganizationsCache(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}

public List<OrganizationDto> fuzzySearchOrganizations(Either<TokenDto, UsernamePasswordDto> credentials, String searchText, SonarLintCancelMonitor cancelMonitor) {
Expand All @@ -74,7 +74,7 @@ public TextSearchIndex<OrganizationDto> getTextSearchIndex(Either<TokenDto, User
LOG.debug("Load user organizations...");
List<OrganizationDto> orgs;
try {
var serverApi = serverApiProvider.getForSonarCloudNoOrg(credentials);
var serverApi = connectionManager.getForSonarCloudNoOrg(credentials);
var serverOrganizations = serverApi.organization().listUserOrganizations(cancelMonitor);
orgs = serverOrganizations.stream().map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).collect(Collectors.toList());
} catch (Exception e) {
Expand Down Expand Up @@ -103,7 +103,7 @@ public List<OrganizationDto> listUserOrganizations(Either<TokenDto, UsernamePass

@CheckForNull
public OrganizationDto getOrganization(Either<TokenDto, UsernamePasswordDto> credentials, String organizationKey, SonarLintCancelMonitor cancelMonitor) {
var helper = serverApiProvider.getForSonarCloudNoOrg(credentials);
var helper = connectionManager.getForSonarCloudNoOrg(credentials);
var serverOrganization = helper.organization().getOrganization(organizationKey, cancelMonitor);
return serverOrganization.map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).orElse(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
public class SonarProjectsCache {

private static final SonarLintLogger LOG = SonarLintLogger.get();
private final ServerApiProvider serverApiProvider;
private final ConnectionManager connectionManager;

private final Cache<String, TextSearchIndex<ServerProject>> textSearchIndexCacheByConnectionId = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
Expand Down Expand Up @@ -94,8 +94,8 @@ public int hashCode() {
}
}

public SonarProjectsCache(ServerApiProvider serverApiProvider) {
this.serverApiProvider = serverApiProvider;
public SonarProjectsCache(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}

@EventListener
Expand All @@ -120,7 +120,8 @@ public Optional<ServerProject> getSonarProject(String connectionId, String sonar
return singleProjectsCache.get(new SonarProjectKey(connectionId, sonarProjectKey), () -> {
LOG.debug("Query project '{}' on connection '{}'...", sonarProjectKey, connectionId);
try {
return serverApiProvider.getServerApi(connectionId).flatMap(s -> s.component().getProject(sonarProjectKey, cancelMonitor));
return connectionManager.withValidConnectionAndReturn(connectionId,
s -> s.component().getProject(sonarProjectKey, cancelMonitor)).orElse(Optional.empty());
} catch (Exception e) {
LOG.error("Error while querying project '{}' from connection '{}'", sonarProjectKey, connectionId, e);
return Optional.empty();
Expand All @@ -137,7 +138,9 @@ public TextSearchIndex<ServerProject> getTextSearchIndex(String connectionId, So
LOG.debug("Load projects from connection '{}'...", connectionId);
List<ServerProject> projects;
try {
projects = serverApiProvider.getServerApi(connectionId).map(s -> s.component().getAllProjects(cancelMonitor)).orElse(List.of());
projects = connectionManager.withValidConnectionAndReturn(connectionId,
s -> s.component().getAllProjects(cancelMonitor))
.orElse(List.of());
} catch (Exception e) {
LOG.error("Error while querying projects from connection '{}'", connectionId, e);
return new TextSearchIndex<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,17 @@ public class VersionSoonUnsupportedHelper {
private final SonarLintRpcClient client;
private final ConfigurationRepository configRepository;
private final ConnectionConfigurationRepository connectionRepository;
private final ServerApiProvider serverApiProvider;
private final ConnectionManager connectionManager;
private final SynchronizationService synchronizationService;
private final Map<String, Version> cacheConnectionIdPerVersion = new ConcurrentHashMap<>();
private final ExecutorServiceShutdownWatchable<?> executorService;

public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, ServerApiProvider serverApiProvider,
public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, ConnectionManager connectionManager,
ConnectionConfigurationRepository connectionRepository, SynchronizationService synchronizationService) {
this.client = client;
this.configRepository = configRepository;
this.connectionRepository = connectionRepository;
this.serverApiProvider = serverApiProvider;
this.connectionManager = connectionManager;
this.synchronizationService = synchronizationService;
this.executorService = new ExecutorServiceShutdownWatchable<>(new ThreadPoolExecutor(0, 1, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), r -> new Thread(r, "Version Soon Unsupported Helper")));
Expand Down Expand Up @@ -107,23 +107,23 @@ private void queueCheckIfSoonUnsupported(String connectionId, String configScope
try {
var connection = connectionRepository.getConnectionById(connectionId);
if (connection != null && connection.getKind() == ConnectionKind.SONARQUBE) {
var serverApi = serverApiProvider.getServerApiWithoutCredentials(connectionId);
if (serverApi.isPresent()) {
var version = synchronizationService.getServerConnection(connectionId, serverApi.get()).readOrSynchronizeServerVersion(serverApi.get(), cancelMonitor);
var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0;
if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) {
client.showSoonUnsupportedMessage(
new ShowSoonUnsupportedMessageParams(
String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()),
configScopeId,
String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts())
)
);
LOG.debug(String.format("Connection '%s' with version '%s' is detected to be soon unsupported",
connection.getConnectionId(), version.getName()));
}
cacheConnectionIdPerVersion.put(connectionId, version);
}
connectionManager.tryGetConnectionWithoutCredentials(connectionId)
.ifPresent(serverConnection -> serverConnection.withClientApi(serverApi -> {
var version = synchronizationService.readOrSynchronizeServerVersion(connectionId, serverApi, cancelMonitor);
var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0;
if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) {
client.showSoonUnsupportedMessage(
new ShowSoonUnsupportedMessageParams(
String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()),
configScopeId,
String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts())
)
);
LOG.debug(String.format("Connection '%s' with version '%s' is detected to be soon unsupported",
connection.getConnectionId(), version.getName()));
}
cacheConnectionIdPerVersion.put(connectionId, version);
}));
}
} catch (Exception e) {
LOG.error("Error while checking if soon unsupported", e);
Expand Down
Loading
Loading