Skip to content

Commit faedf81

Browse files
SLCORE-1032 Wrong token error during sync (#1187)
1 parent 48c8438 commit faedf81

File tree

44 files changed

+673
-298
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+673
-298
lines changed

API_CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Breaking changes
44

5+
* 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
6+
* Client can implement this method to offer user to change credentials for the connection to fix the problem
7+
* 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
8+
* Also once notification sent, backend doesn't attempt to send any requests to server anymore until credentials changed
9+
510
* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient#didRaiseIssue` and associated types. See `raiseIssues` and `raiseHotspots` instead.
611
* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getIssueTrackingService` and associated types. Tracking is managed by the backend.
712
* Removed `org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcServer#getSecurityHotspotMatchingService` and associated types. Tracking is managed by the backend.

backend/core/src/main/java/org/sonarsource/sonarlint/core/ServerApiProvider.java renamed to backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionManager.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,21 @@
2121

2222
import java.net.URI;
2323
import java.util.Optional;
24+
import java.util.function.Consumer;
25+
import java.util.function.Function;
2426
import javax.annotation.Nullable;
2527
import javax.inject.Named;
2628
import javax.inject.Singleton;
2729
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
2830
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
2931
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
32+
import org.sonarsource.sonarlint.core.connection.ServerConnection;
3033
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
3134
import org.sonarsource.sonarlint.core.http.ConnectionAwareHttpClientProvider;
3235
import org.sonarsource.sonarlint.core.http.HttpClient;
3336
import org.sonarsource.sonarlint.core.http.HttpClientProvider;
3437
import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;
38+
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;
3539
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;
3640
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarCloudConnectionDto;
3741
import org.sonarsource.sonarlint.core.rpc.protocol.backend.connection.common.TransientSonarQubeConnectionDto;
@@ -47,19 +51,21 @@
4751

4852
@Named
4953
@Singleton
50-
public class ServerApiProvider {
54+
public class ConnectionManager {
5155

5256
private static final SonarLintLogger LOG = SonarLintLogger.get();
5357
private final ConnectionConfigurationRepository connectionRepository;
5458
private final ConnectionAwareHttpClientProvider awareHttpClientProvider;
5559
private final HttpClientProvider httpClientProvider;
60+
private final SonarLintRpcClient client;
5661
private final URI sonarCloudUri;
5762

58-
public ServerApiProvider(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider,
59-
SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {
63+
public ConnectionManager(ConnectionConfigurationRepository connectionRepository, ConnectionAwareHttpClientProvider awareHttpClientProvider, HttpClientProvider httpClientProvider,
64+
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, SonarLintRpcClient client) {
6065
this.connectionRepository = connectionRepository;
6166
this.awareHttpClientProvider = awareHttpClientProvider;
6267
this.httpClientProvider = httpClientProvider;
68+
this.client = client;
6369
this.sonarCloudUri = sonarCloudActiveEnvironment.getUri();
6470
}
6571

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

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

146+
/**
147+
* Throws ResponseErrorException if connection with provided ID is not found in ConnectionConfigurationRepository
148+
*/
149+
public ServerConnection getConnectionOrThrow(String connectionId) {
150+
var serverApi = getServerApiOrThrow(connectionId);
151+
return new ServerConnection(connectionId, serverApi, client);
152+
}
153+
154+
/**
155+
* Returns empty Optional if connection with provided ID is not found in ConnectionConfigurationRepository
156+
*/
157+
public Optional<ServerConnection> tryGetConnection(String connectionId) {
158+
return getServerApi(connectionId)
159+
.map(serverApi -> new ServerConnection(connectionId, serverApi, client));
160+
}
161+
162+
/**
163+
* Should be used for WebAPI requests without an authentication
164+
*/
165+
public Optional<ServerConnection> tryGetConnectionWithoutCredentials(String connectionId) {
166+
return getServerApiWithoutCredentials(connectionId)
167+
.map(serverApi -> new ServerConnection(connectionId, serverApi, client));
168+
}
169+
170+
public ServerApi getTransientConnection(String token,@Nullable String organization, String baseUrl) {
171+
return getServerApi(baseUrl, organization, token);
172+
}
173+
174+
public void withValidConnection(String connectionId, Consumer<ServerApi> serverApiConsumer) {
175+
getValidConnection(connectionId).ifPresent(connection -> connection.withClientApi(serverApiConsumer));
176+
}
177+
178+
public <T> Optional<T> withValidConnectionAndReturn(String connectionId, Function<ServerApi, T> serverApiConsumer) {
179+
return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer));
180+
}
181+
182+
public <T> Optional<T> withValidConnectionFlatMapOptionalAndReturn(String connectionId, Function<ServerApi, Optional<T>> serverApiConsumer) {
183+
return getValidConnection(connectionId).map(connection -> connection.withClientApiAndReturn(serverApiConsumer)).flatMap(Function.identity());
184+
}
185+
186+
private Optional<ServerConnection> getValidConnection(String connectionId) {
187+
return tryGetConnection(connectionId).filter(ServerConnection::isValid)
188+
.or(() -> {
189+
LOG.debug("Connection '{}' is invalid", connectionId);
190+
return Optional.empty();
191+
});
192+
}
140193
}

backend/core/src/main/java/org/sonarsource/sonarlint/core/ConnectionService.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,23 @@ public class ConnectionService {
6363
private final ApplicationEventPublisher applicationEventPublisher;
6464
private final ConnectionConfigurationRepository repository;
6565
private final URI sonarCloudUri;
66-
private final ServerApiProvider serverApiProvider;
66+
private final ConnectionManager connectionManager;
6767
private final TokenGeneratorHelper tokenGeneratorHelper;
6868

6969
@Inject
7070
public ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository, InitializeParams params,
71-
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, ServerApiProvider serverApiProvider) {
72-
this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, serverApiProvider,
71+
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, TokenGeneratorHelper tokenGeneratorHelper, ConnectionManager connectionManager) {
72+
this(applicationEventPublisher, repository, params.getSonarQubeConnections(), params.getSonarCloudConnections(), sonarCloudActiveEnvironment, connectionManager,
7373
tokenGeneratorHelper);
7474
}
7575

7676
ConnectionService(ApplicationEventPublisher applicationEventPublisher, ConnectionConfigurationRepository repository,
7777
@Nullable List<SonarQubeConnectionConfigurationDto> initSonarQubeConnections, @Nullable List<SonarCloudConnectionConfigurationDto> initSonarCloudConnections,
78-
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ServerApiProvider serverApiProvider, TokenGeneratorHelper tokenGeneratorHelper) {
78+
SonarCloudActiveEnvironment sonarCloudActiveEnvironment, ConnectionManager connectionManager, TokenGeneratorHelper tokenGeneratorHelper) {
7979
this.applicationEventPublisher = applicationEventPublisher;
8080
this.repository = repository;
8181
this.sonarCloudUri = sonarCloudActiveEnvironment.getUri();
82-
this.serverApiProvider = serverApiProvider;
82+
this.connectionManager = connectionManager;
8383
this.tokenGeneratorHelper = tokenGeneratorHelper;
8484
if (initSonarQubeConnections != null) {
8585
initSonarQubeConnections.forEach(c -> repository.addOrReplace(adapt(c)));
@@ -157,7 +157,7 @@ private void updateConnection(AbstractConnectionConfiguration connectionConfigur
157157

158158
public ValidateConnectionResponse validateConnection(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,
159159
SonarLintCancelMonitor cancelMonitor) {
160-
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
160+
var serverApi = connectionManager.getForTransientConnection(transientConnection);
161161
var serverChecker = new ServerVersionAndStatusChecker(serverApi);
162162
try {
163163
serverChecker.checkVersionAndStatus(cancelMonitor);
@@ -179,7 +179,7 @@ public ValidateConnectionResponse validateConnection(Either<TransientSonarQubeCo
179179

180180
public boolean checkSmartNotificationsSupported(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,
181181
SonarLintCancelMonitor cancelMonitor) {
182-
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
182+
var serverApi = connectionManager.getForTransientConnection(transientConnection);
183183
var developersApi = serverApi.developers();
184184
return developersApi.isSupported(cancelMonitor);
185185
}
@@ -189,15 +189,15 @@ public HelpGenerateUserTokenResponse helpGenerateUserToken(String serverUrl, Son
189189
}
190190

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

198198
public Map<String, String> getProjectNamesByKey(Either<TransientSonarQubeConnectionDto, TransientSonarCloudConnectionDto> transientConnection,
199199
List<String> projectKeys, SonarLintCancelMonitor cancelMonitor) {
200-
var serverApi = serverApiProvider.getForTransientConnection(transientConnection);
200+
var serverApi = connectionManager.getForTransientConnection(transientConnection);
201201
var projectNamesByKey = new HashMap<String, String>();
202202
projectKeys.forEach(key -> {
203203
var projectName = serverApi.component().getProject(key, cancelMonitor).map(ServerProject::getName).orElse(null);

backend/core/src/main/java/org/sonarsource/sonarlint/core/OrganizationsCache.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@
4747
public class OrganizationsCache {
4848

4949
private static final SonarLintLogger LOG = SonarLintLogger.get();
50-
private final ServerApiProvider serverApiProvider;
50+
private final ConnectionManager connectionManager;
5151

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

56-
public OrganizationsCache(ServerApiProvider serverApiProvider) {
57-
this.serverApiProvider = serverApiProvider;
56+
public OrganizationsCache(ConnectionManager connectionManager) {
57+
this.connectionManager = connectionManager;
5858
}
5959

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

104104
@CheckForNull
105105
public OrganizationDto getOrganization(Either<TokenDto, UsernamePasswordDto> credentials, String organizationKey, SonarLintCancelMonitor cancelMonitor) {
106-
var helper = serverApiProvider.getForSonarCloudNoOrg(credentials);
106+
var helper = connectionManager.getForSonarCloudNoOrg(credentials);
107107
var serverOrganization = helper.organization().getOrganization(organizationKey, cancelMonitor);
108108
return serverOrganization.map(o -> new OrganizationDto(o.getKey(), o.getName(), o.getDescription())).orElse(null);
109109
}

backend/core/src/main/java/org/sonarsource/sonarlint/core/SonarProjectsCache.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
public class SonarProjectsCache {
4747

4848
private static final SonarLintLogger LOG = SonarLintLogger.get();
49-
private final ServerApiProvider serverApiProvider;
49+
private final ConnectionManager connectionManager;
5050

5151
private final Cache<String, TextSearchIndex<ServerProject>> textSearchIndexCacheByConnectionId = CacheBuilder.newBuilder()
5252
.expireAfterWrite(1, TimeUnit.HOURS)
@@ -94,8 +94,8 @@ public int hashCode() {
9494
}
9595
}
9696

97-
public SonarProjectsCache(ServerApiProvider serverApiProvider) {
98-
this.serverApiProvider = serverApiProvider;
97+
public SonarProjectsCache(ConnectionManager connectionManager) {
98+
this.connectionManager = connectionManager;
9999
}
100100

101101
@EventListener
@@ -120,7 +120,8 @@ public Optional<ServerProject> getSonarProject(String connectionId, String sonar
120120
return singleProjectsCache.get(new SonarProjectKey(connectionId, sonarProjectKey), () -> {
121121
LOG.debug("Query project '{}' on connection '{}'...", sonarProjectKey, connectionId);
122122
try {
123-
return serverApiProvider.getServerApi(connectionId).flatMap(s -> s.component().getProject(sonarProjectKey, cancelMonitor));
123+
return connectionManager.withValidConnectionAndReturn(connectionId,
124+
s -> s.component().getProject(sonarProjectKey, cancelMonitor)).orElse(Optional.empty());
124125
} catch (Exception e) {
125126
LOG.error("Error while querying project '{}' from connection '{}'", sonarProjectKey, connectionId, e);
126127
return Optional.empty();
@@ -137,7 +138,9 @@ public TextSearchIndex<ServerProject> getTextSearchIndex(String connectionId, So
137138
LOG.debug("Load projects from connection '{}'...", connectionId);
138139
List<ServerProject> projects;
139140
try {
140-
projects = serverApiProvider.getServerApi(connectionId).map(s -> s.component().getAllProjects(cancelMonitor)).orElse(List.of());
141+
projects = connectionManager.withValidConnectionAndReturn(connectionId,
142+
s -> s.component().getAllProjects(cancelMonitor))
143+
.orElse(List.of());
141144
} catch (Exception e) {
142145
LOG.error("Error while querying projects from connection '{}'", connectionId, e);
143146
return new TextSearchIndex<>();

backend/core/src/main/java/org/sonarsource/sonarlint/core/VersionSoonUnsupportedHelper.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,17 @@ public class VersionSoonUnsupportedHelper {
5656
private final SonarLintRpcClient client;
5757
private final ConfigurationRepository configRepository;
5858
private final ConnectionConfigurationRepository connectionRepository;
59-
private final ServerApiProvider serverApiProvider;
59+
private final ConnectionManager connectionManager;
6060
private final SynchronizationService synchronizationService;
6161
private final Map<String, Version> cacheConnectionIdPerVersion = new ConcurrentHashMap<>();
6262
private final ExecutorServiceShutdownWatchable<?> executorService;
6363

64-
public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, ServerApiProvider serverApiProvider,
64+
public VersionSoonUnsupportedHelper(SonarLintRpcClient client, ConfigurationRepository configRepository, ConnectionManager connectionManager,
6565
ConnectionConfigurationRepository connectionRepository, SynchronizationService synchronizationService) {
6666
this.client = client;
6767
this.configRepository = configRepository;
6868
this.connectionRepository = connectionRepository;
69-
this.serverApiProvider = serverApiProvider;
69+
this.connectionManager = connectionManager;
7070
this.synchronizationService = synchronizationService;
7171
this.executorService = new ExecutorServiceShutdownWatchable<>(new ThreadPoolExecutor(0, 1, 10L, TimeUnit.SECONDS,
7272
new LinkedBlockingQueue<>(), r -> new Thread(r, "Version Soon Unsupported Helper")));
@@ -107,23 +107,23 @@ private void queueCheckIfSoonUnsupported(String connectionId, String configScope
107107
try {
108108
var connection = connectionRepository.getConnectionById(connectionId);
109109
if (connection != null && connection.getKind() == ConnectionKind.SONARQUBE) {
110-
var serverApi = serverApiProvider.getServerApiWithoutCredentials(connectionId);
111-
if (serverApi.isPresent()) {
112-
var version = synchronizationService.getServerConnection(connectionId, serverApi.get()).readOrSynchronizeServerVersion(serverApi.get(), cancelMonitor);
113-
var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0;
114-
if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) {
115-
client.showSoonUnsupportedMessage(
116-
new ShowSoonUnsupportedMessageParams(
117-
String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()),
118-
configScopeId,
119-
String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts())
120-
)
121-
);
122-
LOG.debug(String.format("Connection '%s' with version '%s' is detected to be soon unsupported",
123-
connection.getConnectionId(), version.getName()));
124-
}
125-
cacheConnectionIdPerVersion.put(connectionId, version);
126-
}
110+
connectionManager.tryGetConnectionWithoutCredentials(connectionId)
111+
.ifPresent(serverConnection -> serverConnection.withClientApi(serverApi -> {
112+
var version = synchronizationService.readOrSynchronizeServerVersion(connectionId, serverApi, cancelMonitor);
113+
var isCached = cacheConnectionIdPerVersion.containsKey(connectionId) && cacheConnectionIdPerVersion.get(connectionId).compareTo(version) == 0;
114+
if (!isCached && VersionUtils.isVersionSupportedDuringGracePeriod(version)) {
115+
client.showSoonUnsupportedMessage(
116+
new ShowSoonUnsupportedMessageParams(
117+
String.format(UNSUPPORTED_NOTIFICATION_ID, connectionId, version.getName()),
118+
configScopeId,
119+
String.format(NOTIFICATION_MESSAGE, version.getName(), connectionId, VersionUtils.getCurrentLts())
120+
)
121+
);
122+
LOG.debug(String.format("Connection '%s' with version '%s' is detected to be soon unsupported",
123+
connection.getConnectionId(), version.getName()));
124+
}
125+
cacheConnectionIdPerVersion.put(connectionId, version);
126+
}));
127127
}
128128
} catch (Exception e) {
129129
LOG.error("Error while checking if soon unsupported", e);

0 commit comments

Comments
 (0)