From 5dc735487ccaa4fd9557a2bc99b1654756c65d8d Mon Sep 17 00:00:00 2001 From: Matteo Mara Date: Fri, 20 May 2022 12:18:46 +0200 Subject: [PATCH] SONAR-16260 improve project analysis when using project analysis token --- .../java/org/sonar/db/user/UserTokenDto.java | 10 ++ .../org/sonar/db/user/UserTokenMapper.xml | 3 +- .../org/sonar/db/user/UserTokenDaoTest.java | 1 + .../authentication/BasicAuthentication.java | 43 +---- .../HttpHeadersAuthentication.java | 5 +- .../RequestAuthenticatorImpl.java | 44 +++-- .../server/authentication/UserAuthResult.java | 63 +++++++ .../sonar/server/user/TokenUserSession.java | 76 ++++++++ .../sonar/server/user/UserSessionFactory.java | 3 + .../server/user/UserSessionFactoryImpl.java | 9 + .../usertoken/UserTokenAuthentication.java | 138 ++++++-------- .../BasicAuthenticationTest.java | 62 +++---- .../RequestAuthenticatorImplTest.java | 50 ++++-- .../server/user/TokenUserSessionTest.java | 170 ++++++++++++++++++ .../UserTokenAuthenticationTest.java | 153 +++++++++++----- .../server/user/TestUserSessionFactory.java | 6 + .../bootstrap/DefaultScannerWsClient.java | 20 +-- .../scanner/bootstrap/ScannerWsClient.java | 8 +- .../bootstrap/ScannerWsClientProvider.java | 2 +- .../java/org/sonar/scanner/WsTestUtil.java | 15 +- .../bootstrap/DefaultScannerWsClientTest.java | 53 ++---- .../scanner/bootstrap/PluginFilesTest.java | 4 +- .../cache/DefaultAnalysisCacheLoaderTest.java | 10 +- .../qualitygate/QualityGateCheckTest.java | 16 +- .../scanner/report/ReportPublisherTest.java | 23 ++- .../DefaultProjectRepositoriesLoaderTest.java | 5 +- .../DefaultGlobalSettingsLoaderTest.java | 2 +- .../DefaultProjectSettingsLoaderTest.java | 4 +- 28 files changed, 658 insertions(+), 340 deletions(-) create mode 100644 server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserAuthResult.java create mode 100644 server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java create mode 100644 server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java index c3630bbc7ea..9e1d2df4b25 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java @@ -45,6 +45,8 @@ public class UserTokenDto { private String projectName; + private String projectUuid; + public String getUuid() { return uuid; } @@ -126,4 +128,12 @@ public class UserTokenDto { this.projectName = projectName; return this; } + + public String getProjectUuid() { + return projectUuid; + } + + public void setProjectUuid(String projectUuid) { + this.projectUuid = projectUuid; + } } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml index ba43d789d86..0536971d2d7 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml @@ -12,7 +12,8 @@ t.created_at as "createdAt", t.project_key as "projectKey", t.type as "type", - p.name as "projectName" + p.name as "projectName", + p.uuid as "projectUuid" diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java index 2f5dc9c7d89..3c142fe13d0 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java @@ -69,6 +69,7 @@ public class UserTokenDaoTest { assertThat(projectAnalysisTokenFromDb.getCreatedAt()).isEqualTo(projectAnalysisToken.getCreatedAt()); assertThat(projectAnalysisTokenFromDb.getTokenHash()).isEqualTo(projectAnalysisToken.getTokenHash()); assertThat(projectAnalysisTokenFromDb.getUserUuid()).isEqualTo(projectAnalysisToken.getUserUuid()); + assertThat(projectAnalysisTokenFromDb.getProjectUuid()).isEqualTo(project.uuid()); assertThat(projectAnalysisTokenFromDb.getProjectKey()).isEqualTo(projectAnalysisToken.getProjectKey()); assertThat(projectAnalysisTokenFromDb.getType()).isEqualTo(projectAnalysisToken.getType()); assertThat(projectAnalysisTokenFromDb.getProjectName()).isEqualTo(project.name()); diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/BasicAuthentication.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/BasicAuthentication.java index b5ac6571cba..b95eab152b9 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/BasicAuthentication.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/BasicAuthentication.java @@ -22,8 +22,6 @@ package org.sonar.server.authentication; import java.util.Base64; import java.util.Optional; import javax.servlet.http.HttpServletRequest; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; @@ -33,7 +31,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.commons.lang.StringUtils.startsWithIgnoreCase; import static org.sonar.server.authentication.event.AuthenticationEvent.Method; import static org.sonar.server.authentication.event.AuthenticationEvent.Source; -import static org.sonar.server.usertoken.UserTokenAuthentication.PROJECT_KEY_SCANNER_HEADER; /** * HTTP BASIC authentication relying on tuple {login, password}. @@ -44,24 +41,17 @@ import static org.sonar.server.usertoken.UserTokenAuthentication.PROJECT_KEY_SCA */ public class BasicAuthentication { - private static final String ACCESS_LOG_TOKEN_NAME = "TOKEN_NAME"; - - private final DbClient dbClient; private final CredentialsAuthentication credentialsAuthentication; private final UserTokenAuthentication userTokenAuthentication; - private final AuthenticationEvent authenticationEvent; - public BasicAuthentication(DbClient dbClient, CredentialsAuthentication credentialsAuthentication, - UserTokenAuthentication userTokenAuthentication, AuthenticationEvent authenticationEvent) { - this.dbClient = dbClient; + public BasicAuthentication(CredentialsAuthentication credentialsAuthentication, UserTokenAuthentication userTokenAuthentication) { this.credentialsAuthentication = credentialsAuthentication; this.userTokenAuthentication = userTokenAuthentication; - this.authenticationEvent = authenticationEvent; } public Optional authenticate(HttpServletRequest request) { return extractCredentialsFromHeader(request) - .flatMap(credentials -> Optional.of(authenticate(credentials, request))); + .flatMap(credentials -> Optional.ofNullable(authenticate(credentials, request))); } public static Optional extractCredentialsFromHeader(HttpServletRequest request) { @@ -98,34 +88,17 @@ public class BasicAuthentication { private UserDto authenticate(Credentials credentials, HttpServletRequest request) { if (credentials.getPassword().isEmpty()) { - String projectKeyScannerHeader = request.getHeader(PROJECT_KEY_SCANNER_HEADER); - UserDto userDto = authenticateFromUserToken(credentials.getLogin(), request, projectKeyScannerHeader); - authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.local(Method.BASIC_TOKEN)); - return userDto; - } - return credentialsAuthentication.authenticate(credentials, request, Method.BASIC); - } - - private UserDto authenticateFromUserToken(String token, HttpServletRequest request, String projectKey) { - String path = request.getRequestURI().substring(request.getContextPath().length()); - UserTokenAuthentication.UserTokenAuthenticationResult result = userTokenAuthentication.authenticate(token, path, projectKey); - if (result.getErrorMessage() != null) { - throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.BASIC_TOKEN)) - .setMessage(result.getErrorMessage()) - .build(); - } - try (DbSession dbSession = dbClient.openSession(false)) { - UserDto userDto = dbClient.userDao().selectByUuid(dbSession, result.getAuthenticatedUserUuid()); - if (userDto == null || !userDto.isActive()) { + Optional userAuthResult = userTokenAuthentication.authenticate(request); + if (userAuthResult.isPresent()) { + return userAuthResult.get().getUserDto(); + } else { throw AuthenticationException.newBuilder() - .setSource(Source.local(Method.BASIC_TOKEN)) + .setSource(AuthenticationEvent.Source.local(AuthenticationEvent.Method.BASIC_TOKEN)) .setMessage("User doesn't exist") .build(); } - request.setAttribute(ACCESS_LOG_TOKEN_NAME, result.getTokenName()); - return userDto; } + return credentialsAuthentication.authenticate(credentials, request, Method.BASIC); } } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java index 1562bd7e3b3..f24e9d721ce 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java @@ -20,7 +20,6 @@ package org.sonar.server.authentication; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableMap; import java.util.Collections; import java.util.Date; import java.util.EnumSet; @@ -133,14 +132,14 @@ public class HttpHeadersAuthentication implements Startable { } UserDto userDto = doAuthenticate(headerValuesByNames, login); - jwtHttpHandler.generateToken(userDto, ImmutableMap.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response); + jwtHttpHandler.generateToken(userDto, Map.of(LAST_REFRESH_TIME_TOKEN_PARAM, system2.now()), request, response); authenticationEvent.loginSuccess(request, userDto.getLogin(), Source.sso()); return Optional.of(userDto); } private Optional getUserFromToken(HttpServletRequest request, HttpServletResponse response) { Optional token = jwtHttpHandler.getToken(request, response); - if (!token.isPresent()) { + if (token.isEmpty()) { return Optional.empty(); } Date now = new Date(system2.now()); diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java index 4f7871f594a..ade1d51f905 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java @@ -27,30 +27,41 @@ import javax.servlet.http.HttpServletResponse; import org.sonar.db.user.UserDto; import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSessionFactory; +import org.sonar.server.usertoken.UserTokenAuthentication; import org.springframework.beans.factory.annotation.Autowired; +import static java.util.Objects.nonNull; +import static org.sonar.server.authentication.UserAuthResult.AuthType.BASIC; +import static org.sonar.server.authentication.UserAuthResult.AuthType.JWT; +import static org.sonar.server.authentication.UserAuthResult.AuthType.SSO; +import static org.sonar.server.authentication.UserAuthResult.AuthType.TOKEN; + public class RequestAuthenticatorImpl implements RequestAuthenticator { private final JwtHttpHandler jwtHttpHandler; private final BasicAuthentication basicAuthentication; + private final UserTokenAuthentication userTokenAuthentication; private final HttpHeadersAuthentication httpHeadersAuthentication; private final UserSessionFactory userSessionFactory; private final List customAuthentications; @Autowired(required = false) - public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, HttpHeadersAuthentication httpHeadersAuthentication, + public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, UserTokenAuthentication userTokenAuthentication, + HttpHeadersAuthentication httpHeadersAuthentication, UserSessionFactory userSessionFactory, CustomAuthentication[] customAuthentications) { this.jwtHttpHandler = jwtHttpHandler; this.basicAuthentication = basicAuthentication; + this.userTokenAuthentication = userTokenAuthentication; this.httpHeadersAuthentication = httpHeadersAuthentication; this.userSessionFactory = userSessionFactory; this.customAuthentications = Arrays.asList(customAuthentications); } @Autowired(required = false) - public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, HttpHeadersAuthentication httpHeadersAuthentication, + public RequestAuthenticatorImpl(JwtHttpHandler jwtHttpHandler, BasicAuthentication basicAuthentication, UserTokenAuthentication userTokenAuthentication, + HttpHeadersAuthentication httpHeadersAuthentication, UserSessionFactory userSessionFactory) { - this(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, userSessionFactory, new CustomAuthentication[0]); + this(jwtHttpHandler, basicAuthentication, userTokenAuthentication, httpHeadersAuthentication, userSessionFactory, new CustomAuthentication[0]); } @Override @@ -62,25 +73,38 @@ public class RequestAuthenticatorImpl implements RequestAuthenticator { } } - Optional userOpt = loadUser(request, response); - if (userOpt.isPresent()) { - return userSessionFactory.create(userOpt.get()); + UserAuthResult userAuthResult = loadUser(request, response); + if (nonNull(userAuthResult.getUserDto())) { + if (TOKEN.equals(userAuthResult.getAuthType())) { + return userSessionFactory.create(userAuthResult.getUserDto(), userAuthResult.getTokenDto()); + } + return userSessionFactory.create(userAuthResult.getUserDto()); } return userSessionFactory.createAnonymous(); } - private Optional loadUser(HttpServletRequest request, HttpServletResponse response) { + private UserAuthResult loadUser(HttpServletRequest request, HttpServletResponse response) { // Try first to authenticate from SSO, then JWT token, then try from basic http header // SSO authentication should come first in order to update JWT if user from header is not the same is user from JWT Optional user = httpHeadersAuthentication.authenticate(request, response); if (user.isPresent()) { - return user; + return new UserAuthResult(user.get(), SSO); } user = jwtHttpHandler.validateToken(request, response); if (user.isPresent()) { - return user; + return new UserAuthResult(user.get(), JWT); + } + + // Check if the authentication is token based + Optional userAuthResult = userTokenAuthentication.authenticate(request); + if (userAuthResult.isPresent()) { + return userAuthResult.get(); } - return basicAuthentication.authenticate(request); + + user = basicAuthentication.authenticate(request); + return user.map(userDto -> new UserAuthResult(userDto, BASIC)) + .orElseGet(UserAuthResult::new); } + } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserAuthResult.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserAuthResult.java new file mode 100644 index 00000000000..7f5cafcbecc --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserAuthResult.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.authentication; + +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; + +public class UserAuthResult { + + public enum AuthType { + SSO, + JWT, + TOKEN, + BASIC + } + + UserDto userDto; + UserTokenDto tokenDto; + AuthType authType; + + public UserAuthResult() { + } + + public UserAuthResult(UserDto userDto, AuthType authType) { + this.userDto = userDto; + this.authType = authType; + } + + public UserAuthResult(UserDto userDto, UserTokenDto tokenDto, AuthType authType) { + this.userDto = userDto; + this.tokenDto = tokenDto; + this.authType = authType; + } + + public UserDto getUserDto() { + return userDto; + } + + public AuthType getAuthType() { + return authType; + } + + public UserTokenDto getTokenDto() { + return tokenDto; + } +} diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java new file mode 100644 index 00000000000..7f0b730aa7e --- /dev/null +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.user; + +import org.sonar.db.DbClient; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.user.TokenType; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; + +public class TokenUserSession extends ServerUserSession { + + private static final String SCAN = "scan"; + private final UserTokenDto userToken; + + public TokenUserSession(DbClient dbClient, UserDto user, UserTokenDto userToken) { + super(dbClient, user); + this.userToken = userToken; + } + + @Override + protected boolean hasProjectUuidPermission(String permission, String projectUuid) { + TokenType tokenType = TokenType.valueOf(userToken.getType()); + switch (tokenType) { + case USER_TOKEN: + return super.hasProjectUuidPermission(permission, projectUuid); + case PROJECT_ANALYSIS_TOKEN: + return SCAN.equals(permission) && + projectUuid.equals(userToken.getProjectUuid()) && + (super.hasProjectUuidPermission(SCAN, projectUuid) || super.hasPermissionImpl(GlobalPermission.SCAN)); + case GLOBAL_ANALYSIS_TOKEN: + //The case with a global analysis token has to return false always, since it is based on the assumption that the user + // has global analysis privileges + return false; + default: + throw new IllegalArgumentException("Unsupported token type " + tokenType.name()); + } + + } + + @Override + protected boolean hasPermissionImpl(GlobalPermission permission) { + TokenType tokenType = TokenType.valueOf(userToken.getType()); + switch (tokenType) { + case USER_TOKEN: + return super.hasPermissionImpl(permission); + case PROJECT_ANALYSIS_TOKEN: + //The case with a project analysis token has to return false always, delegating the result to the super class would allow + //the project analysis token to work for multiple projects in case the user has Global Permissions. + return false; + case GLOBAL_ANALYSIS_TOKEN: + return GlobalPermission.SCAN.equals(permission) && + super.hasPermissionImpl(permission); + default: + throw new IllegalArgumentException("Unsupported token type " + tokenType.name()); + } + } + +} diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactory.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactory.java index 1995b8cfbee..c7cf98fccd7 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactory.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactory.java @@ -21,12 +21,15 @@ package org.sonar.server.user; import org.sonar.api.server.ServerSide; import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; @ServerSide public interface UserSessionFactory { UserSession create(UserDto user); + UserSession create(UserDto user, UserTokenDto userToken); + UserSession createAnonymous(); } diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java index 3e049d4ae23..ca32c8afd51 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java @@ -22,6 +22,7 @@ package org.sonar.server.user; import org.sonar.api.server.ServerSide; import org.sonar.db.DbClient; import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; import org.sonar.server.authentication.UserLastConnectionDatesUpdater; import static java.util.Objects.requireNonNull; @@ -44,6 +45,14 @@ public class UserSessionFactoryImpl implements UserSessionFactory { return new ServerUserSession(dbClient, user); } + @Override + public TokenUserSession create(UserDto user, UserTokenDto userToken) { + requireNonNull(user, "UserDto must not be null"); + requireNonNull(userToken, "UserTokenDto must not be null"); + userLastConnectionDatesUpdater.updateLastConnectionDateIfNeeded(user); + return new TokenUserSession(dbClient, user, userToken); + } + @Override public ServerUserSession createAnonymous() { return new ServerUserSession(dbClient, null); diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java index 77eba8c2def..be066b6a422 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java @@ -19,120 +19,88 @@ */ package org.sonar.server.usertoken; -import java.util.EnumMap; -import java.util.Set; +import java.util.Optional; import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; import org.sonar.db.DbClient; import org.sonar.db.DbSession; -import org.sonar.db.user.TokenType; +import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; +import org.sonar.server.authentication.Credentials; +import org.sonar.server.authentication.UserAuthResult; import org.sonar.server.authentication.UserLastConnectionDatesUpdater; +import org.sonar.server.authentication.event.AuthenticationEvent; +import org.sonar.server.authentication.event.AuthenticationException; +import org.sonar.server.exceptions.NotFoundException; -public class UserTokenAuthentication { - - public static final String PROJECT_KEY_SCANNER_HEADER = "PROJECT_KEY"; - - private static final Set SCANNER_ENDPOINTS = Set.of( - "/api/settings/values", - "/api/plugins/installed", - "/api/analysis_cache/get", - "/api/project_branches/list", - "/api/project_pull_requests/list", - "/api/qualityprofiles/search", - "/api/rules/search", - "/batch/project", - "/api/metrics/search", - "/api/new_code_periods/show", - "/api/ce/submit"); +import static org.sonar.server.authentication.BasicAuthentication.extractCredentialsFromHeader; - private static final EnumMap> ALLOWLIST_ENDPOINTS_FOR_TOKEN_TYPES = new EnumMap<>(TokenType.class); - - static { - ALLOWLIST_ENDPOINTS_FOR_TOKEN_TYPES.put(TokenType.GLOBAL_ANALYSIS_TOKEN, SCANNER_ENDPOINTS); - ALLOWLIST_ENDPOINTS_FOR_TOKEN_TYPES.put(TokenType.PROJECT_ANALYSIS_TOKEN, SCANNER_ENDPOINTS); - } +public class UserTokenAuthentication { + private static final String ACCESS_LOG_TOKEN_NAME = "TOKEN_NAME"; private final TokenGenerator tokenGenerator; private final DbClient dbClient; private final UserLastConnectionDatesUpdater userLastConnectionDatesUpdater; + private final AuthenticationEvent authenticationEvent; - public UserTokenAuthentication(TokenGenerator tokenGenerator, DbClient dbClient, UserLastConnectionDatesUpdater userLastConnectionDatesUpdater) { + public UserTokenAuthentication(TokenGenerator tokenGenerator, DbClient dbClient, UserLastConnectionDatesUpdater userLastConnectionDatesUpdater, + AuthenticationEvent authenticationEvent) { this.tokenGenerator = tokenGenerator; this.dbClient = dbClient; this.userLastConnectionDatesUpdater = userLastConnectionDatesUpdater; + this.authenticationEvent = authenticationEvent; } - /** - * Returns the user token details including if the token hash is found and the user has provided valid token type. - * - * The returned uuid included in the UserTokenAuthenticationResult is not validated. If database is corrupted - * (table USER_TOKENS badly purged for instance), then the uuid may not relate to a valid user. - * - * In case of any issues only the error message is included in UserTokenAuthenticationResult - */ - public UserTokenAuthenticationResult authenticate(String token, String requestedEndpoint, @Nullable String analyzedProjectKey) { - String tokenHash = tokenGenerator.hash(token); - try (DbSession dbSession = dbClient.openSession(false)) { - UserTokenDto userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash); - if (userToken == null) { - return new UserTokenAuthenticationResult("Token doesn't exist"); - } - if (!isValidTokenType(userToken, analyzedProjectKey, requestedEndpoint)) { - return new UserTokenAuthenticationResult("Invalid token"); + public Optional authenticate(HttpServletRequest request) { + if (isTokenBasedAuthentication(request)) { + Optional credentials = extractCredentialsFromHeader(request); + if (credentials.isPresent()) { + UserAuthResult userAuthResult = authenticateFromUserToken(credentials.get().getLogin(), request); + authenticationEvent.loginSuccess(request, userAuthResult.getUserDto().getLogin(), AuthenticationEvent.Source.local(AuthenticationEvent.Method.BASIC_TOKEN)); + return Optional.of(userAuthResult); } - userLastConnectionDatesUpdater.updateLastConnectionDateIfNeeded(userToken); - return new UserTokenAuthenticationResult(userToken.getUserUuid(), userToken.getName()); } + return Optional.empty(); } - private static boolean isValidTokenType(UserTokenDto userToken, @Nullable String analyzedProjectKey, String requestedEndpoint) { - TokenType tokenType = TokenType.valueOf(userToken.getType()); - - return validateProjectKeyForScannerToken(tokenType, userToken, analyzedProjectKey) - && shouldBeAbleToAccessEndpoint(tokenType, requestedEndpoint); + public static boolean isTokenBasedAuthentication(HttpServletRequest request) { + Optional credentialsOptional = extractCredentialsFromHeader(request); + return credentialsOptional.map(credentials -> credentials.getPassword().isEmpty()).orElse(false); } - private static boolean shouldBeAbleToAccessEndpoint(TokenType tokenType, String requestedEndpoint) { - Set allowedEndpoints = ALLOWLIST_ENDPOINTS_FOR_TOKEN_TYPES.get(tokenType); - if (allowedEndpoints == null) { - return true; // no allowlist configured for the token type - all endpoints are allowed + private UserAuthResult authenticateFromUserToken(String token, HttpServletRequest request) { + try (DbSession dbSession = dbClient.openSession(false)) { + UserTokenDto userToken = authenticate(token); + UserDto userDto = dbClient.userDao().selectByUuid(dbSession, userToken.getUserUuid()); + if (userDto == null || !userDto.isActive()) { + throw AuthenticationException.newBuilder() + .setSource(AuthenticationEvent.Source.local(AuthenticationEvent.Method.BASIC_TOKEN)) + .setMessage("User doesn't exist") + .build(); + } + request.setAttribute(ACCESS_LOG_TOKEN_NAME, userToken.getName()); + return new UserAuthResult(userDto, userToken, UserAuthResult.AuthType.TOKEN); + } catch (NotFoundException exception) { + throw AuthenticationException.newBuilder() + .setSource(AuthenticationEvent.Source.local(AuthenticationEvent.Method.BASIC_TOKEN)) + .setMessage(exception.getMessage()) + .build(); } - return allowedEndpoints.stream().anyMatch(requestedEndpoint::startsWith); } - private static boolean validateProjectKeyForScannerToken(TokenType tokenType, UserTokenDto userToken, @Nullable String analyzedProjectKey) { - if (tokenType != TokenType.PROJECT_ANALYSIS_TOKEN) { - return true; + private UserTokenDto authenticate(String token) { + UserTokenDto userToken = getUserToken(token); + if (userToken == null) { + throw new NotFoundException("Token doesn't exist"); } - return analyzedProjectKey != null && analyzedProjectKey.equals(userToken.getProjectKey()); + userLastConnectionDatesUpdater.updateLastConnectionDateIfNeeded(userToken); + return userToken; } - public static class UserTokenAuthenticationResult { - - String authenticatedUserUuid; - String errorMessage; - String tokenName; - - public UserTokenAuthenticationResult(String authenticatedUserUuid, String tokenName) { - this.authenticatedUserUuid = authenticatedUserUuid; - this.tokenName = tokenName; - } - - public UserTokenAuthenticationResult(String errorMessage) { - this.errorMessage = errorMessage; - } - - public String getAuthenticatedUserUuid() { - return authenticatedUserUuid; - } - - public String getErrorMessage() { - return errorMessage; - } - - public String getTokenName() { - return tokenName; + @Nullable + public UserTokenDto getUserToken(String token) { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.userTokenDao().selectByTokenHash(dbSession, tokenGenerator.hash(token)); } - } } diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java index 05c2231f4c7..01cde7bb264 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java @@ -26,10 +26,10 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; import org.sonar.db.DbTester; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTesting; +import org.sonar.db.user.UserTokenDto; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; import org.sonar.server.usertoken.UserTokenAuthentication; @@ -59,20 +59,19 @@ public class BasicAuthenticationTest { private static final UserDto USER = UserTesting.newUserDto().setLogin(A_LOGIN); private static final String EXAMPLE_ENDPOINT = "/api/ce/submit"; + private static final String AUTHORIZATION_HEADER = "Authorization"; @Rule public DbTester db = DbTester.create(System2.INSTANCE); - private DbClient dbClient = db.getDbClient(); + private final CredentialsAuthentication credentialsAuthentication = mock(CredentialsAuthentication.class); + private final UserTokenAuthentication userTokenAuthentication = mock(UserTokenAuthentication.class); - private CredentialsAuthentication credentialsAuthentication = mock(CredentialsAuthentication.class); - private UserTokenAuthentication userTokenAuthentication = mock(UserTokenAuthentication.class); + private final HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletRequest request = mock(HttpServletRequest.class); + private final AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); - - private BasicAuthentication underTest = new BasicAuthentication(dbClient, credentialsAuthentication, userTokenAuthentication, authenticationEvent); + private final BasicAuthentication underTest = new BasicAuthentication(credentialsAuthentication, userTokenAuthentication); @Before public void before() { @@ -83,7 +82,7 @@ public class BasicAuthenticationTest { @Test public void authenticate_from_basic_http_header() { - when(request.getHeader("Authorization")).thenReturn("Basic " + CREDENTIALS_IN_BASE64); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + CREDENTIALS_IN_BASE64); Credentials credentials = new Credentials(A_LOGIN, A_PASSWORD); when(credentialsAuthentication.authenticate(credentials, request, BASIC)).thenReturn(USER); @@ -96,7 +95,7 @@ public class BasicAuthenticationTest { @Test public void authenticate_from_basic_http_header_with_password_containing_semi_colon() { String password = "!ascii-only:-)@"; - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(A_LOGIN + ":" + password)); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(A_LOGIN + ":" + password)); when(credentialsAuthentication.authenticate(new Credentials(A_LOGIN, password), request, BASIC)).thenReturn(USER); underTest.authenticate(request); @@ -114,7 +113,7 @@ public class BasicAuthenticationTest { @Test public void does_not_authenticate_when_authorization_header_is_not_BASIC() { - when(request.getHeader("Authorization")).thenReturn("OTHER " + CREDENTIALS_IN_BASE64); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("OTHER " + CREDENTIALS_IN_BASE64); underTest.authenticate(request); @@ -123,7 +122,7 @@ public class BasicAuthenticationTest { @Test public void fail_to_authenticate_when_no_login() { - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64(":" + A_PASSWORD)); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(":" + A_PASSWORD)); assertThatThrownBy(() -> underTest.authenticate(request)) .isInstanceOf(AuthenticationException.class) @@ -134,7 +133,7 @@ public class BasicAuthenticationTest { @Test public void fail_to_authenticate_when_invalid_header() { - when(request.getHeader("Authorization")).thenReturn("Basic Invàlid"); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic Invàlid"); assertThatThrownBy(() -> underTest.authenticate(request)) .hasMessage("Invalid basic header") @@ -145,26 +144,22 @@ public class BasicAuthenticationTest { @Test public void authenticate_from_user_token() { UserDto user = db.users().insertUser(); - var result = new UserTokenAuthentication.UserTokenAuthenticationResult(user.getUuid(), "my-token"); - when(userTokenAuthentication.authenticate("token", EXAMPLE_ENDPOINT, null)).thenReturn(result); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); + when(userTokenAuthentication.authenticate(request)).thenReturn(Optional.of(new UserAuthResult(user, new UserTokenDto().setName("my-token"), UserAuthResult.AuthType.TOKEN))); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("token:")); Optional userAuthenticated = underTest.authenticate(request); assertThat(userAuthenticated).isPresent(); assertThat(userAuthenticated.get().getLogin()).isEqualTo(user.getLogin()); - verify(authenticationEvent).loginSuccess(request, user.getLogin(), Source.local(BASIC_TOKEN)); - verify(request).setAttribute("TOKEN_NAME", "my-token"); } @Test public void does_not_authenticate_from_user_token_when_token_is_invalid() { - var result = new UserTokenAuthentication.UserTokenAuthenticationResult("Token doesn't exist"); - when(userTokenAuthentication.authenticate("token", EXAMPLE_ENDPOINT, null)).thenReturn(result); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); + when(userTokenAuthentication.authenticate(request)).thenReturn(Optional.empty()); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("token:")); assertThatThrownBy(() -> underTest.authenticate(request)) - .hasMessage("Token doesn't exist") + .hasMessage("User doesn't exist") .isInstanceOf(AuthenticationException.class) .hasFieldOrPropertyWithValue("source", Source.local(BASIC_TOKEN)); @@ -174,24 +169,11 @@ public class BasicAuthenticationTest { @Test public void does_not_authenticate_from_user_token_when_token_does_not_match_existing_user() { - var result = new UserTokenAuthentication.UserTokenAuthenticationResult("unknown-user-uuid", "my-token"); - when(userTokenAuthentication.authenticate("token", EXAMPLE_ENDPOINT, null)).thenReturn(result); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); - - assertThatThrownBy(() -> underTest.authenticate(request)) - .hasMessageContaining("User doesn't exist") - .isInstanceOf(AuthenticationException.class) - .hasFieldOrPropertyWithValue("source", Source.local(BASIC_TOKEN)); - - verifyNoInteractions(authenticationEvent); - } - - @Test - public void does_not_authenticate_from_user_token_when_token_does_not_match_active_user() { - UserDto user = db.users().insertDisabledUser(); - var result = new UserTokenAuthentication.UserTokenAuthenticationResult(user.getUuid(), "my-token"); - when(userTokenAuthentication.authenticate("token", EXAMPLE_ENDPOINT, null)).thenReturn(result); - when(request.getHeader("Authorization")).thenReturn("Basic " + toBase64("token:")); + when(userTokenAuthentication.authenticate(request)).thenThrow(AuthenticationException.newBuilder() + .setSource(AuthenticationEvent.Source.local(AuthenticationEvent.Method.BASIC_TOKEN)) + .setMessage("User doesn't exist") + .build()); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("token:")); assertThatThrownBy(() -> underTest.authenticate(request)) .hasMessageContaining("User doesn't exist") diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java index 2f79f6028ca..9fa95233e55 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java @@ -25,12 +25,14 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; import org.sonar.server.tester.AnonymousMockUserSession; import org.sonar.server.tester.MockUserSession; import org.sonar.server.user.UserSession; import org.sonar.server.user.UserSessionFactory; +import org.sonar.server.usertoken.UserTokenAuthentication; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -38,26 +40,30 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.db.user.TokenType.USER_TOKEN; import static org.sonar.db.user.UserTesting.newUserDto; public class RequestAuthenticatorImplTest { private static final UserDto A_USER = newUserDto(); - - private HttpServletRequest request = mock(HttpServletRequest.class); - private HttpServletResponse response = mock(HttpServletResponse.class); - private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); - private BasicAuthentication basicAuthentication = mock(BasicAuthentication.class); - private HttpHeadersAuthentication httpHeadersAuthentication = mock(HttpHeadersAuthentication.class); - private UserSessionFactory sessionFactory = mock(UserSessionFactory.class); - private CustomAuthentication customAuthentication1 = mock(CustomAuthentication.class); - private CustomAuthentication customAuthentication2 = mock(CustomAuthentication.class); - private RequestAuthenticator underTest = new RequestAuthenticatorImpl(jwtHttpHandler, basicAuthentication, httpHeadersAuthentication, sessionFactory, + private static final UserTokenDto A_USER_TOKEN = mockUserTokenDto(A_USER); + + private final HttpServletRequest request = mock(HttpServletRequest.class); + private final HttpServletResponse response = mock(HttpServletResponse.class); + private final JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class); + private final BasicAuthentication basicAuthentication = mock(BasicAuthentication.class); + private final UserTokenAuthentication userTokenAuthentication = mock(UserTokenAuthentication.class); + private final HttpHeadersAuthentication httpHeadersAuthentication = mock(HttpHeadersAuthentication.class); + private final UserSessionFactory sessionFactory = mock(UserSessionFactory.class); + private final CustomAuthentication customAuthentication1 = mock(CustomAuthentication.class); + private final CustomAuthentication customAuthentication2 = mock(CustomAuthentication.class); + private final RequestAuthenticator underTest = new RequestAuthenticatorImpl(jwtHttpHandler, basicAuthentication, userTokenAuthentication, httpHeadersAuthentication, sessionFactory, new CustomAuthentication[]{customAuthentication1, customAuthentication2}); @Before public void setUp() { when(sessionFactory.create(A_USER)).thenReturn(new MockUserSession(A_USER)); + when(sessionFactory.create(A_USER, A_USER_TOKEN)).thenReturn(new MockUserSession(A_USER)); when(sessionFactory.createAnonymous()).thenReturn(new AnonymousMockUserSession()); } @@ -83,6 +89,21 @@ public class RequestAuthenticatorImplTest { verify(response, never()).setStatus(anyInt()); } + @Test + public void authenticate_from_basic_token() { + when(request.getHeader("Authorization")).thenReturn("Basic dGVzdDo="); + when(userTokenAuthentication.getUserToken("test")).thenReturn(A_USER_TOKEN); + when(userTokenAuthentication.authenticate(request)).thenReturn(Optional.of(new UserAuthResult(A_USER, A_USER_TOKEN, UserAuthResult.AuthType.TOKEN))); + when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.empty()); + when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty()); + + assertThat(underTest.authenticate(request, response).getUuid()).isEqualTo(A_USER.getUuid()); + + verify(jwtHttpHandler).validateToken(request, response); + verify(userTokenAuthentication).authenticate(request); + verify(response, never()).setStatus(anyInt()); + } + @Test public void authenticate_from_sso() { when(httpHeadersAuthentication.authenticate(request, response)).thenReturn(Optional.of(A_USER)); @@ -131,4 +152,13 @@ public class RequestAuthenticatorImplTest { assertThat(session.getLogin()).isEqualTo("foo"); } + + private static UserTokenDto mockUserTokenDto(UserDto userDto) { + UserTokenDto userTokenDto = new UserTokenDto(); + userTokenDto.setType(USER_TOKEN.name()); + userTokenDto.setName("User Token"); + userTokenDto.setUserUuid(userDto.getUuid()); + return userTokenDto; + } + } diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java new file mode 100644 index 00000000000..6ffe52c179c --- /dev/null +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java @@ -0,0 +1,170 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.user; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.web.UserRole.SCAN; +import static org.sonar.db.user.TokenType.GLOBAL_ANALYSIS_TOKEN; +import static org.sonar.db.user.TokenType.PROJECT_ANALYSIS_TOKEN; +import static org.sonar.db.user.TokenType.USER_TOKEN; + +public class TokenUserSessionTest { + + @Rule + public final DbTester db = DbTester.create(System2.INSTANCE); + private final DbClient dbClient = db.getDbClient(); + + @Test + public void test_hasProjectsPermission_for_UserToken() { + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + + db.users().insertProjectPermissionOnUser(user, SCAN, project1); + + TokenUserSession userSession = mockTokenUserSession(user); + + assertThat(userSession.hasProjectUuidPermission(SCAN, project1.projectUuid())).isTrue(); + assertThat(userSession.hasProjectUuidPermission(SCAN, project2.projectUuid())).isFalse(); + } + + @Test + public void test_hasProjectsPermission_for_ProjecAnalysisToken() { + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + + db.users().insertProjectPermissionOnUser(user, SCAN, project1); + db.users().insertProjectPermissionOnUser(user, SCAN, project2); + + TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1); + + assertThat(userSession.hasProjectUuidPermission(SCAN, project1.projectUuid())).isTrue(); + assertThat(userSession.hasProjectUuidPermission(SCAN, project2.projectUuid())).isFalse(); + } + + @Test + public void test_hasProjectsPermission_for_ProjectAnalysisToken_with_global_permission() { + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + + db.users().insertPermissionOnUser(user, GlobalPermission.SCAN); + + TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1); + + assertThat(userSession.hasProjectUuidPermission(SCAN, project1.projectUuid())).isTrue(); + assertThat(userSession.hasProjectUuidPermission(SCAN, project2.projectUuid())).isFalse(); + } + + @Test + public void test_hasGlobalPermission_for_UserToken() { + UserDto user = db.users().insertUser(); + db.users().insertPermissionOnUser(user, GlobalPermission.SCAN); + + TokenUserSession userSession = mockTokenUserSession(user); + + assertThat(userSession.hasPermission(GlobalPermission.SCAN)).isTrue(); + } + + @Test + public void test_hasGlobalPermission_for_ProjecAnalysisToken() { + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + + db.users().insertProjectPermissionOnUser(user, SCAN, project1); + db.users().insertProjectPermissionOnUser(user, SCAN, project2); + + db.users().insertPermissionOnUser(user, GlobalPermission.SCAN); + + TokenUserSession userSession = mockProjectAnalysisTokenUserSession(user,project1); + + assertThat(userSession.hasPermission(GlobalPermission.SCAN)).isFalse(); + } + + @Test + public void test_hasGlobalPermission_for_GlobalAnalysisToken() { + ComponentDto project1 = db.components().insertPrivateProject(); + + UserDto user = db.users().insertUser(); + + db.users().insertPermissionOnUser(user, GlobalPermission.SCAN); + + TokenUserSession userSession = mockGlobalAnalysisTokenUserSession(user); + + assertThat(userSession.hasProjectUuidPermission(SCAN, project1.projectUuid())).isFalse(); + assertThat(userSession.hasPermission(GlobalPermission.SCAN)).isTrue(); + } + + private TokenUserSession mockTokenUserSession(UserDto userDto) { + return new TokenUserSession(dbClient, userDto, mockUserTokenDto()); + } + + private TokenUserSession mockProjectAnalysisTokenUserSession(UserDto userDto, ComponentDto componentDto) { + return new TokenUserSession(dbClient, userDto, mockProjectAnalysisTokenDto(componentDto)); + } + + private TokenUserSession mockGlobalAnalysisTokenUserSession(UserDto userDto) { + return new TokenUserSession(dbClient, userDto, mockGlobalAnalysisTokenDto()); + } + + private UserTokenDto mockUserTokenDto() { + UserTokenDto userTokenDto = new UserTokenDto(); + userTokenDto.setType(USER_TOKEN.name()); + userTokenDto.setName("User Token"); + userTokenDto.setUserUuid("userUid"); + return userTokenDto; + } + + private UserTokenDto mockProjectAnalysisTokenDto(ComponentDto componentDto) { + UserTokenDto userTokenDto = new UserTokenDto(); + userTokenDto.setType(PROJECT_ANALYSIS_TOKEN.name()); + userTokenDto.setName("Project Analysis Token"); + userTokenDto.setUserUuid("userUid"); + userTokenDto.setProjectKey(componentDto.getKey()); + userTokenDto.setProjectName(componentDto.name()); + userTokenDto.setProjectUuid(componentDto.projectUuid()); + return userTokenDto; + } + + private UserTokenDto mockGlobalAnalysisTokenDto() { + UserTokenDto userTokenDto = new UserTokenDto(); + userTokenDto.setType(GLOBAL_ANALYSIS_TOKEN.name()); + userTokenDto.setName("Global Analysis Token"); + userTokenDto.setUserUuid("userUid"); + return userTokenDto; + } + +} diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java index 32b2c3948ba..3e353f20e28 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java @@ -19,30 +19,41 @@ */ package org.sonar.server.usertoken; +import java.util.Base64; +import java.util.Optional; +import javax.servlet.http.HttpServletRequest; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; -import org.sonar.db.user.TokenType; import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; +import org.sonar.server.authentication.UserAuthResult; import org.sonar.server.authentication.UserLastConnectionDatesUpdater; -import org.sonar.server.usertoken.UserTokenAuthentication.UserTokenAuthenticationResult; +import org.sonar.server.authentication.event.AuthenticationEvent; +import org.sonar.server.authentication.event.AuthenticationException; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import static org.sonar.db.user.TokenType.GLOBAL_ANALYSIS_TOKEN; +import static org.sonar.db.user.TokenType.PROJECT_ANALYSIS_TOKEN; +import static org.sonar.db.user.TokenType.USER_TOKEN; +import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN; +import static org.sonar.server.usertoken.UserTokenAuthentication.isTokenBasedAuthentication; public class UserTokenAuthenticationTest { - private static final String EXAMPLE_SCANNER_ENDPOINT = "/api/settings/values.protobuf"; - private static final String EXAMPLE_USER_ENDPOINT = "/api/editions/set_license"; + private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder(); - private static final String EXAMPLE_PROJECT_KEY = "my-project-key"; + private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String EXAMPLE_OLD_USER_TOKEN = "StringWith40CharactersThatIsOldUserToken"; private static final String EXAMPLE_NEW_USER_TOKEN = "squ_StringWith44CharactersThatIsNewUserToken"; @@ -57,13 +68,15 @@ public class UserTokenAuthenticationTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); - private TokenGenerator tokenGenerator = mock(TokenGenerator.class); - private UserLastConnectionDatesUpdater userLastConnectionDatesUpdater = mock(UserLastConnectionDatesUpdater.class); - - private UserTokenAuthentication underTest = new UserTokenAuthentication(tokenGenerator, db.getDbClient(), userLastConnectionDatesUpdater); + private final TokenGenerator tokenGenerator = mock(TokenGenerator.class); + private final UserLastConnectionDatesUpdater userLastConnectionDatesUpdater = mock(UserLastConnectionDatesUpdater.class); + private final AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class); + private final HttpServletRequest request = mock(HttpServletRequest.class); + private final UserTokenAuthentication underTest = new UserTokenAuthentication(tokenGenerator, db.getDbClient(), userLastConnectionDatesUpdater, authenticationEvent); @Before public void before() { + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("token:")); when(tokenGenerator.hash(EXAMPLE_OLD_USER_TOKEN)).thenReturn(OLD_USER_TOKEN_HASH); when(tokenGenerator.hash(EXAMPLE_NEW_USER_TOKEN)).thenReturn(NEW_USER_TOKEN_HASH); when(tokenGenerator.hash(EXAMPLE_PROJECT_ANALYSIS_TOKEN)).thenReturn(PROJECT_ANALYSIS_TOKEN_HASH); @@ -74,93 +87,135 @@ public class UserTokenAuthenticationTest { public void return_login_when_token_hash_found_in_db() { String token = "known-token"; String tokenHash = "123456789"; + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(token + ":")); when(tokenGenerator.hash(token)).thenReturn(tokenHash); UserDto user1 = db.users().insertUser(); - db.users().insertToken(user1, t -> t.setTokenHash(tokenHash)); + UserTokenDto userTokenDto = db.users().insertToken(user1, t -> t.setTokenHash(tokenHash)); UserDto user2 = db.users().insertUser(); db.users().insertToken(user2, t -> t.setTokenHash("another-token-hash")); - UserTokenAuthenticationResult result = underTest.authenticate(token, EXAMPLE_USER_ENDPOINT, null); + Optional result = underTest.authenticate(request); - assertThat(result.getAuthenticatedUserUuid()) + assertThat(result).isPresent(); + assertThat(result.get().getTokenDto().getUuid()).isEqualTo(userTokenDto.getUuid()); + assertThat(result.get().getUserDto().getUuid()) .isNotNull() .contains(user1.getUuid()); verify(userLastConnectionDatesUpdater).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); } + @Test + public void return_absent_if_username_password_used() { + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("login:password")); + + Optional result = underTest.authenticate(request); + + assertThat(result).isEmpty(); + verify(userLastConnectionDatesUpdater, never()).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); + verifyNoInteractions(authenticationEvent); + } + @Test public void return_absent_if_token_hash_is_not_found() { - var result = underTest.authenticate(EXAMPLE_OLD_USER_TOKEN, EXAMPLE_USER_ENDPOINT, null); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_OLD_USER_TOKEN + ":")); - assertThat(result.getAuthenticatedUserUuid()).isNull(); + assertThatThrownBy(() -> underTest.authenticate(request)) + .hasMessageContaining("Token doesn't exist") + .isInstanceOf(AuthenticationException.class); verify(userLastConnectionDatesUpdater, never()).updateLastConnectionDateIfNeeded(any(UserTokenDto.class)); + verifyNoInteractions(authenticationEvent); } @Test - public void authenticate_givenProjectTokenAndUserEndpoint_fillErrorMessage() { + public void authenticate_givenGlobalToken_resultContainsUuid() { UserDto user = db.users().insertUser(); - db.users().insertToken(user, t -> t.setTokenHash(PROJECT_ANALYSIS_TOKEN_HASH).setType(TokenType.PROJECT_ANALYSIS_TOKEN.name())); + String tokenName = db.users().insertToken(user, t -> t.setTokenHash(GLOBAL_ANALYSIS_TOKEN_HASH).setType(GLOBAL_ANALYSIS_TOKEN.name())).getName(); - var authenticate = underTest.authenticate(EXAMPLE_PROJECT_ANALYSIS_TOKEN, EXAMPLE_USER_ENDPOINT, EXAMPLE_PROJECT_KEY); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_GLOBAL_ANALYSIS_TOKEN + ":")); + var result = underTest.authenticate(request); - assertThat(authenticate.getErrorMessage()).isNotNull().contains("Invalid token"); + assertThat(result).isPresent(); + assertThat(result.get().getTokenDto().getUuid()).isNotNull(); + assertThat(result.get().getTokenDto().getType()).isEqualTo(GLOBAL_ANALYSIS_TOKEN.name()); + verify(authenticationEvent).loginSuccess(request, user.getLogin(), AuthenticationEvent.Source.local(BASIC_TOKEN)); + verify(request).setAttribute("TOKEN_NAME",tokenName); } @Test - public void authenticate_givenProjectTokenAndUserEndpoint_InvalidTokenErrorMessage() { + public void authenticate_givenNewUserToken_resultContainsUuid() { UserDto user = db.users().insertUser(); - db.users().insertToken(user, t -> t.setTokenHash(PROJECT_ANALYSIS_TOKEN_HASH).setType(TokenType.PROJECT_ANALYSIS_TOKEN.name())); + String tokenName = db.users().insertToken(user, t -> t.setTokenHash(NEW_USER_TOKEN_HASH).setType(USER_TOKEN.name())).getName(); - var result = underTest.authenticate(EXAMPLE_PROJECT_ANALYSIS_TOKEN, EXAMPLE_USER_ENDPOINT, EXAMPLE_PROJECT_KEY); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_NEW_USER_TOKEN + ":")); + var result = underTest.authenticate(request); - assertThat(result.getErrorMessage()).isNotNull().contains("Invalid token"); + assertThat(result).isPresent(); + assertThat(result.get().getTokenDto().getUuid()).isNotNull(); + assertThat(result.get().getTokenDto().getType()).isEqualTo(USER_TOKEN.name()); + verify(authenticationEvent).loginSuccess(request, user.getLogin(), AuthenticationEvent.Source.local(BASIC_TOKEN)); + verify(request).setAttribute("TOKEN_NAME",tokenName); } @Test - public void authenticate_givenGlobalTokenAndScannerEndpoint_resultContainsUuid() { + public void authenticate_givenProjectToken_resultContainsUuid() { UserDto user = db.users().insertUser(); - db.users().insertToken(user, t -> t.setTokenHash(GLOBAL_ANALYSIS_TOKEN_HASH).setType(TokenType.GLOBAL_ANALYSIS_TOKEN.name())); + String tokenName = db.users().insertToken(user, t -> t.setTokenHash(PROJECT_ANALYSIS_TOKEN_HASH) + .setProjectKey("project-key") + .setType(PROJECT_ANALYSIS_TOKEN.name())).getName(); - var result = underTest.authenticate(EXAMPLE_GLOBAL_ANALYSIS_TOKEN, EXAMPLE_SCANNER_ENDPOINT, EXAMPLE_PROJECT_KEY); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_PROJECT_ANALYSIS_TOKEN + ":")); + var result = underTest.authenticate(request); - assertThat(result.getAuthenticatedUserUuid()).isNotNull(); - assertThat(result.getErrorMessage()).isNull(); + assertThat(result).isPresent(); + assertThat(result.get().getTokenDto().getUuid()).isNotNull(); + assertThat(result.get().getTokenDto().getType()).isEqualTo(PROJECT_ANALYSIS_TOKEN.name()); + assertThat(result.get().getTokenDto().getProjectKey()).isEqualTo("project-key"); + verify(authenticationEvent).loginSuccess(request, user.getLogin(), AuthenticationEvent.Source.local(BASIC_TOKEN)); + verify(request).setAttribute("TOKEN_NAME",tokenName); } + @Test - public void authenticate_givenNewUserTokenAndScannerEndpoint_resultContainsUuid() { - UserDto user = db.users().insertUser(); - db.users().insertToken(user, t -> t.setTokenHash(NEW_USER_TOKEN_HASH).setType(TokenType.USER_TOKEN.name())); + public void does_not_authenticate_from_user_token_when_token_does_not_match_active_user() { + UserDto user = db.users().insertDisabledUser(); + String tokenName = db.users().insertToken(user, t -> t.setTokenHash(NEW_USER_TOKEN_HASH).setType(USER_TOKEN.name())).getName(); + + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64(EXAMPLE_NEW_USER_TOKEN + ":")); - var result = underTest.authenticate(EXAMPLE_NEW_USER_TOKEN, EXAMPLE_SCANNER_ENDPOINT, null); + assertThatThrownBy(() -> underTest.authenticate(request)) + .hasMessageContaining("User doesn't exist") + .isInstanceOf(AuthenticationException.class) + .hasFieldOrPropertyWithValue("source", AuthenticationEvent.Source.local(BASIC_TOKEN)); - assertThat(result.getAuthenticatedUserUuid()).isNotNull(); - assertThat(result.getErrorMessage()).isNull(); + verifyNoInteractions(authenticationEvent); } @Test - public void authenticate_givenProjectTokenAndScannerEndpointAndValidProjectKey_resultContainsUuid() { - UserDto user = db.users().insertUser(); - db.users().insertToken(user, t -> t.setTokenHash(PROJECT_ANALYSIS_TOKEN_HASH) - .setProjectKey("project-key") - .setType(TokenType.PROJECT_ANALYSIS_TOKEN.name())); + public void return_token_from_db() { + String token = "known-token"; + String tokenHash = "123456789"; + when(tokenGenerator.hash(token)).thenReturn(tokenHash); + UserDto user1 = db.users().insertUser(); + UserTokenDto userTokenDto = db.users().insertToken(user1, t -> t.setTokenHash(tokenHash)); - var result = underTest.authenticate(EXAMPLE_PROJECT_ANALYSIS_TOKEN, EXAMPLE_SCANNER_ENDPOINT, "project-key"); + UserTokenDto result = underTest.getUserToken(token); - assertThat(result.getAuthenticatedUserUuid()).isNotNull(); - assertThat(result.getErrorMessage()).isNull(); + assertThat(result.getUuid()).isEqualTo(userTokenDto.getUuid()); } @Test - public void authenticate_givenProjectTokenAndScannerEndpointAndWrongProjectKey_resultContainsErrorMessage() { - UserDto user = db.users().insertUser(); - db.users().insertToken(user, t -> t.setTokenHash(PROJECT_ANALYSIS_TOKEN_HASH) - .setProjectKey("project-key") - .setType(TokenType.PROJECT_ANALYSIS_TOKEN.name())); + public void identifies_if_request_uses_token_based_authentication() { + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("token:")); + assertThat(isTokenBasedAuthentication(request)).isTrue(); - var result = underTest.authenticate(EXAMPLE_PROJECT_ANALYSIS_TOKEN, EXAMPLE_SCANNER_ENDPOINT, "project-key-2"); + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn("Basic " + toBase64("login:password")); + assertThat(isTokenBasedAuthentication(request)).isFalse(); + + when(request.getHeader(AUTHORIZATION_HEADER)).thenReturn(null); + assertThat(isTokenBasedAuthentication(request)).isFalse(); + } - assertThat(result.getAuthenticatedUserUuid()).isNull(); - assertThat(result.getErrorMessage()).isNotNull(); + private static String toBase64(String text) { + return new String(BASE64_ENCODER.encode(text.getBytes(UTF_8))); } } diff --git a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java index d09d9ea662d..92d1d056781 100644 --- a/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java +++ b/server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java @@ -25,6 +25,7 @@ import javax.annotation.Nullable; import org.sonar.db.permission.GlobalPermission; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserTokenDto; import static java.util.Objects.requireNonNull; @@ -45,6 +46,11 @@ public class TestUserSessionFactory implements UserSessionFactory { return new TestUserSession(requireNonNull(user)); } + @Override + public UserSession create(UserDto user, UserTokenDto userToken) { + return new TestUserSession(requireNonNull(user)); + } + @Override public UserSession createAnonymous() { return new TestUserSession(null); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java index a554aa5c238..8dc5dc3a39f 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java @@ -32,9 +32,7 @@ import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; -import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; -import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.WsConnector; import org.sonarqube.ws.client.WsRequest; @@ -50,18 +48,14 @@ public class DefaultScannerWsClient implements ScannerWsClient { private static final int MAX_ERROR_MSG_LEN = 128; private static final Logger LOG = Loggers.get(DefaultScannerWsClient.class); - private static final String PROJECT_KEY_CONTEXT_HEADER = "PROJECT_KEY"; - private final WsClient target; private final boolean hasCredentials; private final GlobalAnalysisMode globalMode; - private final ScannerProperties scannerProperties; - public DefaultScannerWsClient(WsClient target, boolean hasCredentials, GlobalAnalysisMode globalMode, ScannerProperties scannerProperties) { + public DefaultScannerWsClient(WsClient target, boolean hasCredentials, GlobalAnalysisMode globalMode) { this.target = target; this.hasCredentials = hasCredentials; this.globalMode = globalMode; - this.scannerProperties = scannerProperties; } /** @@ -73,7 +67,7 @@ public class DefaultScannerWsClient implements ScannerWsClient { * @throws MessageException if there was a problem with authentication or if a error message was parsed from the response. * @throws HttpException if the response code is not in range [200..300). Consider using {@link #createErrorMessage(HttpException)} to create more relevant messages for the users. */ - private WsResponse getResponse(WsRequest request) { + public WsResponse call(WsRequest request) { checkState(!globalMode.isMediumTest(), "No WS call should be made in medium test mode"); Profiler profiler = Profiler.createIfDebug(LOG).start(); WsResponse response = target.wsConnector().call(request); @@ -82,16 +76,6 @@ public class DefaultScannerWsClient implements ScannerWsClient { return response; } - public WsResponse call(GetRequest getRequest) { - getRequest.setHeader(PROJECT_KEY_CONTEXT_HEADER, scannerProperties.getProjectKey()); - return getResponse(getRequest); - } - - public WsResponse call(PostRequest postRequest) { - postRequest.setHeader(PROJECT_KEY_CONTEXT_HEADER, scannerProperties.getProjectKey()); - return getResponse(postRequest); - } - public String baseUrl() { return target.wsConnector().baseUrl(); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClient.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClient.java index bd8452f4ad1..a11326008ed 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClient.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClient.java @@ -19,16 +19,12 @@ */ package org.sonar.scanner.bootstrap; -import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsRequest; import org.sonarqube.ws.client.WsResponse; public interface ScannerWsClient { - WsResponse call(GetRequest request); - - WsResponse call(PostRequest request); + WsResponse call(WsRequest request); String baseUrl(); - } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java index 606785aaf8a..8e86aa8b0b4 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java @@ -58,6 +58,6 @@ public class ScannerWsClientProvider { } return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null, - globalMode, scannerProps); + globalMode); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java index 157c0745b5e..45905619f03 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java @@ -25,7 +25,6 @@ import javax.annotation.Nullable; import org.apache.commons.lang.StringUtils; import org.mockito.ArgumentMatcher; import org.sonar.scanner.bootstrap.DefaultScannerWsClient; -import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsRequest; import org.sonarqube.ws.client.WsResponse; @@ -39,19 +38,19 @@ public class WsTestUtil { public static void mockStream(DefaultScannerWsClient mock, String path, InputStream is) { WsResponse response = mock(WsResponse.class); when(response.contentStream()).thenReturn(is); - when(mock.call((GetRequest) argThat(new RequestMatcher(path)))).thenReturn(response); + when(mock.call(argThat(new RequestMatcher(path)))).thenReturn(response); } public static void mockStream(DefaultScannerWsClient mock, InputStream is) { WsResponse response = mock(WsResponse.class); when(response.contentStream()).thenReturn(is); - when(mock.call(any(GetRequest.class))).thenReturn(response); + when(mock.call(any(WsRequest.class))).thenReturn(response); } public static void mockReader(DefaultScannerWsClient mock, Reader reader) { WsResponse response = mock(WsResponse.class); when(response.contentReader()).thenReturn(reader); - when(mock.call(any(GetRequest.class))).thenReturn(response); + when(mock.call(any(WsRequest.class))).thenReturn(response); } public static void mockReader(DefaultScannerWsClient mock, String path, Reader reader, Reader... others) { @@ -64,19 +63,19 @@ public class WsTestUtil { otherResponses[i] = otherResponse; } - when(mock.call((GetRequest) argThat(new RequestMatcher(path)))).thenReturn(response, otherResponses); + when(mock.call(argThat(new RequestMatcher(path)))).thenReturn(response, otherResponses); } public static void mockException(DefaultScannerWsClient mock, Exception e) { - when(mock.call(any(GetRequest.class))).thenThrow(e); + when(mock.call(any(WsRequest.class))).thenThrow(e); } public static void mockException(DefaultScannerWsClient mock, String path, Exception e) { - when(mock.call((GetRequest) argThat(new RequestMatcher(path)))).thenThrow(e); + when(mock.call(argThat(new RequestMatcher(path)))).thenThrow(e); } public static void verifyCall(DefaultScannerWsClient mock, String path) { - verify(mock).call((GetRequest) argThat(new RequestMatcher(path))); + verify(mock).call(argThat(new RequestMatcher(path))); } private static class RequestMatcher implements ArgumentMatcher { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java index 20dc164f075..752b6ef0891 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java @@ -19,9 +19,9 @@ */ package org.sonar.scanner.bootstrap; +import java.util.Collections; import java.util.List; import org.apache.commons.lang.StringUtils; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; @@ -31,8 +31,8 @@ import org.sonar.api.utils.log.LoggerLevel; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.MockWsResponse; -import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsRequest; import org.sonarqube.ws.client.WsResponse; import static org.assertj.core.api.Assertions.assertThat; @@ -47,22 +47,14 @@ public class DefaultScannerWsClientTest { private final WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS); - private final ScannerProperties scannerProperties = mock(ScannerProperties.class); - - @Before - public void before() { - when(scannerProperties.getProjectKey()).thenReturn("projectKey"); - } - @Test public void log_and_profile_request_if_debug_level() { - GetRequest request = newGetRequest(); + WsRequest request = newRequest(); WsResponse response = newResponse().setRequestUrl("https://local/api/issues/search"); when(wsClient.wsConnector().call(request)).thenReturn(response); logTester.setLevel(LoggerLevel.DEBUG); - DefaultScannerWsClient underTest = new DefaultScannerWsClient(wsClient, false, new GlobalAnalysisMode(scannerProperties), - scannerProperties); + DefaultScannerWsClient underTest = new DefaultScannerWsClient(wsClient, false, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))); WsResponse result = underTest.call(request); @@ -75,21 +67,6 @@ public class DefaultScannerWsClientTest { assertThat(debugLogs.get(0)).contains("GET 200 https://local/api/issues/search | time="); } - @Test - public void call_alwaysAddContextHeaders() { - GetRequest getRequest = newGetRequest(); - PostRequest postRequest = newPostRequest(); - - DefaultScannerWsClient underTest = new DefaultScannerWsClient(wsClient, false, new GlobalAnalysisMode(scannerProperties), - scannerProperties); - - underTest.call(getRequest); - underTest.call(postRequest); - - assertThat(getRequest.getHeaders().getValue("PROJECT_KEY")).contains("projectKey"); - assertThat(postRequest.getHeaders().getValue("PROJECT_KEY")).contains("projectKey"); - } - @Test public void create_error_msg_from_json() { String content = "{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}"; @@ -110,12 +87,12 @@ public class DefaultScannerWsClientTest { @Test public void fail_if_requires_credentials() { - GetRequest request = newGetRequest(); + WsRequest request = newRequest(); WsResponse response = newResponse().setCode(401); when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, false, - new GlobalAnalysisMode(scannerProperties), scannerProperties).call(request)) + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) .isInstanceOf(MessageException.class) .hasMessage("Not authorized. Analyzing this project requires authentication. Please provide a user token in sonar.login or other " + "credentials in sonar.login and sonar.password."); @@ -123,39 +100,39 @@ public class DefaultScannerWsClientTest { @Test public void fail_if_credentials_are_not_valid() { - GetRequest request = newGetRequest(); + WsRequest request = newRequest(); WsResponse response = newResponse().setCode(401); when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, /* credentials are configured */true, - new GlobalAnalysisMode(scannerProperties), scannerProperties).call(request)) + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) .isInstanceOf(MessageException.class) .hasMessage("Not authorized. Please check the properties sonar.login and sonar.password."); } @Test public void fail_if_requires_permission() { - GetRequest request = newGetRequest(); + WsRequest request = newRequest(); WsResponse response = newResponse() .setCode(403); when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, true, - new GlobalAnalysisMode(scannerProperties), scannerProperties).call(request)) + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) .isInstanceOf(MessageException.class) .hasMessage("You're not authorized to run analysis. Please contact the project administrator."); } @Test public void fail_if_bad_request() { - GetRequest request = newGetRequest(); + WsRequest request = newRequest(); WsResponse response = newResponse() .setCode(400) .setContent("{\"errors\":[{\"msg\":\"Boo! bad request! bad!\"}]}"); when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, true, - new GlobalAnalysisMode(scannerProperties), scannerProperties).call(request)) + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) .isInstanceOf(MessageException.class) .hasMessage("Boo! bad request! bad!"); } @@ -164,11 +141,7 @@ public class DefaultScannerWsClientTest { return new MockWsResponse().setRequestUrl("https://local/api/issues/search"); } - private GetRequest newGetRequest() { + private WsRequest newRequest() { return new GetRequest("api/issues/search"); } - - private PostRequest newPostRequest() { - return new PostRequest("api/ce/task"); - } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java index 0c726b4035a..d2b7cd4840d 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java @@ -47,7 +47,6 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.batch.fs.internal.DefaultInputProject; import org.sonar.api.config.internal.MapSettings; import org.sonar.scanner.bootstrap.ScannerPluginInstaller.InstalledPlugin; import org.sonarqube.ws.client.HttpConnector; @@ -57,7 +56,6 @@ import static org.apache.commons.io.FileUtils.moveFile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import static org.mockito.Mockito.mock; public class PluginFilesTest { @@ -74,7 +72,7 @@ public class PluginFilesTest { HttpConnector connector = HttpConnector.newBuilder().url(server.url("/").toString()).build(); GlobalAnalysisMode analysisMode = new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())); DefaultScannerWsClient wsClient = new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connector), false, - analysisMode, mock(ScannerProperties.class)); + analysisMode); userHome = temp.newFolder(); MapSettings settings = new MapSettings(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/DefaultAnalysisCacheLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/DefaultAnalysisCacheLoaderTest.java index be8ad554eca..3281d457a7d 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/DefaultAnalysisCacheLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/DefaultAnalysisCacheLoaderTest.java @@ -35,8 +35,8 @@ import org.sonar.api.utils.MessageException; import org.sonar.scanner.bootstrap.DefaultScannerWsClient; import org.sonar.scanner.protocol.internal.ScannerInternal.AnalysisCacheMsg; import org.sonar.scanner.scan.branch.BranchConfiguration; -import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; +import org.sonarqube.ws.client.WsRequest; import org.sonarqube.ws.client.WsResponse; import static org.assertj.core.api.Assertions.assertThat; @@ -60,7 +60,7 @@ public class DefaultAnalysisCacheLoaderTest { @Before public void before() { when(project.key()).thenReturn("myproject"); - when(wsClient.call(any(GetRequest.class))).thenReturn(response); + when(wsClient.call(any())).thenReturn(response); } @Test @@ -91,13 +91,13 @@ public class DefaultAnalysisCacheLoaderTest { @Test public void returns_empty_if_404() { - when(wsClient.call(any(GetRequest.class))).thenThrow(new HttpException("url", 404, "content")); + when(wsClient.call(any())).thenThrow(new HttpException("url", 404, "content")); assertThat(loader.load()).isEmpty(); } @Test public void throw_error_if_http_exception_not_404() { - when(wsClient.call(any(GetRequest.class))).thenThrow(new HttpException("url", 401, "content")); + when(wsClient.call(any())).thenThrow(new HttpException("url", 401, "content")); assertThatThrownBy(loader::load) .isInstanceOf(MessageException.class) .hasMessage("Failed to download analysis cache: HTTP code 401: content"); @@ -112,7 +112,7 @@ public class DefaultAnalysisCacheLoaderTest { } private void assertRequestPath(String expectedPath) { - ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(GetRequest.class); + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(WsRequest.class); verify(wsClient).call(requestCaptor.capture()); assertThat(requestCaptor.getValue().getPath()).isEqualTo(expectedPath); } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java index 89613f6bba0..bd3803a7eb1 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java @@ -38,9 +38,9 @@ import org.sonarqube.ws.Ce; import org.sonarqube.ws.Ce.TaskStatus; import org.sonarqube.ws.Qualitygates; import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status; -import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.MockWsResponse; +import org.sonarqube.ws.client.WsRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -266,8 +266,8 @@ public class QualityGateCheckTest { .hasMessageContaining("CE Task finished abnormally with status: " + taskStatus.name()); } - private GetRequest newGetCeTaskRequest() { - return argThat(new GetRequestPathMatcher("api/ce/task")); + private WsRequest newGetCeTaskRequest() { + return argThat(new WsRequestPathMatcher("api/ce/task")); } private MockWsResponse getCeTaskWsResponse(TaskStatus status) { @@ -302,8 +302,8 @@ public class QualityGateCheckTest { .isInstanceOf(IllegalStateException.class); } - private GetRequest newGetQualityGateRequest() { - return argThat(new GetRequestPathMatcher("api/qualitygates/project_status")); + private WsRequest newGetQualityGateRequest() { + return argThat(new WsRequestPathMatcher("api/qualitygates/project_status")); } private MockWsResponse getQualityGateWsResponse(Status status) { @@ -325,15 +325,15 @@ public class QualityGateCheckTest { }; } - private static class GetRequestPathMatcher implements ArgumentMatcher { + private static class WsRequestPathMatcher implements ArgumentMatcher { String path; - GetRequestPathMatcher(String path) { + WsRequestPathMatcher(String path) { this.path = path; } @Override - public boolean matches(GetRequest right) { + public boolean matches(WsRequest right) { return path.equals(right.getPath()); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java index 55d6772e37e..cc440601a2f 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java @@ -45,7 +45,6 @@ import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonarqube.ws.Ce; import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.MockWsResponse; -import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsRequest; import org.sonarqube.ws.client.WsResponse; @@ -97,12 +96,12 @@ public class ReportPublisherTest { public void use_30s_write_timeout() { MockWsResponse submitMockResponse = new MockWsResponse(); submitMockResponse.setContent(Ce.SubmitResponse.newBuilder().setTaskId("task-1234").build().toByteArray()); - when(wsClient.call(any(PostRequest.class))).thenReturn(submitMockResponse); + when(wsClient.call(any())).thenReturn(submitMockResponse); underTest.start(); underTest.execute(); - verify(wsClient).call((PostRequest) argThat(req -> ((PostRequest) req).getWriteTimeOutInMs().orElse(0) == 30_000)); + verify(wsClient).call(argThat(req -> (req).getWriteTimeOutInMs().orElse(0) == 30_000)); } @Test @@ -123,7 +122,7 @@ public class ReportPublisherTest { HttpException ex = new HttpException("url", 404, "{\"errors\":[{\"msg\":\"Organization with key 'MyOrg' does not exist\"}]}"); WsResponse response = mock(WsResponse.class); when(response.failIfNotSuccessful()).thenThrow(ex); - when(wsClient.call(any(PostRequest.class))).thenThrow(new IllegalStateException("timeout")); + when(wsClient.call(any(WsRequest.class))).thenThrow(new IllegalStateException("timeout")); assertThatThrownBy(() -> underTest.upload(reportTempFolder.newFile())) .isInstanceOf(IllegalStateException.class) @@ -135,7 +134,7 @@ public class ReportPublisherTest { HttpException ex = new HttpException("url", 404, "{\"errors\":[{\"msg\":\"Organization with key 'MyOrg' does not exist\"}]}"); WsResponse response = mock(WsResponse.class); when(response.failIfNotSuccessful()).thenThrow(ex); - when(wsClient.call(any(PostRequest.class))).thenReturn(response); + when(wsClient.call(any(WsRequest.class))).thenReturn(response); assertThatThrownBy(() -> underTest.upload(reportTempFolder.newFile())) .isInstanceOf(MessageException.class) @@ -226,7 +225,7 @@ public class ReportPublisherTest { MockWsResponse submitMockResponse = new MockWsResponse(); submitMockResponse.setContent(Ce.SubmitResponse.newBuilder().setTaskId("task-1234").build().toByteArray()); - when(wsClient.call(any(PostRequest.class))).thenReturn(submitMockResponse); + when(wsClient.call(any())).thenReturn(submitMockResponse); underTest.start(); underTest.execute(); @@ -277,10 +276,10 @@ public class ReportPublisherTest { when(response.failIfNotSuccessful()).thenReturn(response); when(response.contentStream()).thenReturn(in); - when(wsClient.call(any(PostRequest.class))).thenReturn(response); + when(wsClient.call(any(WsRequest.class))).thenReturn(response); underTest.upload(reportTempFolder.newFile()); - ArgumentCaptor capture = ArgumentCaptor.forClass(PostRequest.class); + ArgumentCaptor capture = ArgumentCaptor.forClass(WsRequest.class); verify(wsClient).call(capture.capture()); WsRequest wsRequest = capture.getValue(); @@ -304,10 +303,10 @@ public class ReportPublisherTest { when(response.failIfNotSuccessful()).thenReturn(response); when(response.contentStream()).thenReturn(in); - when(wsClient.call(any(PostRequest.class))).thenReturn(response); + when(wsClient.call(any(WsRequest.class))).thenReturn(response); underTest.upload(reportTempFolder.newFile()); - ArgumentCaptor capture = ArgumentCaptor.forClass(PostRequest.class); + ArgumentCaptor capture = ArgumentCaptor.forClass(WsRequest.class); verify(wsClient).call(capture.capture()); WsRequest wsRequest = capture.getValue(); @@ -335,10 +334,10 @@ public class ReportPublisherTest { when(response.failIfNotSuccessful()).thenReturn(response); when(response.contentStream()).thenReturn(in); - when(wsClient.call(any(PostRequest.class))).thenReturn(response); + when(wsClient.call(any(WsRequest.class))).thenReturn(response); underTest.upload(reportTempFolder.newFile()); - ArgumentCaptor capture = ArgumentCaptor.forClass(PostRequest.class); + ArgumentCaptor capture = ArgumentCaptor.forClass(WsRequest.class); verify(wsClient).call(capture.capture()); WsRequest wsRequest = capture.getValue(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java index ca09e2bd359..829095bc81b 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java @@ -32,7 +32,6 @@ import org.sonar.api.utils.MessageException; import org.sonar.scanner.WsTestUtil; import org.sonar.scanner.bootstrap.DefaultScannerWsClient; import org.sonarqube.ws.Batch.WsProjectResponse; -import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.HttpException; import static org.assertj.core.api.Assertions.assertThat; @@ -58,14 +57,14 @@ public class DefaultProjectRepositoriesLoaderTest { @Test public void continueOnHttp404Exception() { - when(wsClient.call(any(GetRequest.class))).thenThrow(new HttpException("/batch/project.protobuf?key=foo%3F", HttpURLConnection.HTTP_NOT_FOUND, "")); + when(wsClient.call(any())).thenThrow(new HttpException("/batch/project.protobuf?key=foo%3F", HttpURLConnection.HTTP_NOT_FOUND, "")); ProjectRepositories proj = loader.load(PROJECT_KEY, null); assertThat(proj.exists()).isFalse(); } @Test(expected = IllegalStateException.class) public void failOnNonHttp404Exception() { - when(wsClient.call(any(GetRequest.class))).thenThrow(IllegalStateException.class); + when(wsClient.call(any())).thenThrow(IllegalStateException.class); ProjectRepositories proj = loader.load(PROJECT_KEY, null); assertThat(proj.exists()).isFalse(); } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultGlobalSettingsLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultGlobalSettingsLoaderTest.java index daae92c11c2..069d7a85fb2 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultGlobalSettingsLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultGlobalSettingsLoaderTest.java @@ -58,7 +58,7 @@ public class DefaultGlobalSettingsLoaderTest { .writeTo(out); out.close(); when(response.contentStream()).thenReturn(in); - when(wsClient.call(any(GetRequest.class))).thenReturn(response); + when(wsClient.call(any())).thenReturn(response); Map result = underTest.loadGlobalSettings(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java index 9699b433258..ae6e98cd7ac 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java @@ -25,8 +25,8 @@ import java.io.PipedOutputStream; import java.util.Map; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.sonar.scanner.bootstrap.ScannerProperties; import org.sonar.scanner.bootstrap.DefaultScannerWsClient; +import org.sonar.scanner.bootstrap.ScannerProperties; import org.sonarqube.ws.Settings; import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.WsResponse; @@ -60,7 +60,7 @@ public class DefaultProjectSettingsLoaderTest { .writeTo(out); out.close(); when(response.contentStream()).thenReturn(in); - when(wsClient.call(any(GetRequest.class))).thenReturn(response); + when(wsClient.call(any())).thenReturn(response); when(properties.getProjectKey()).thenReturn("project_key"); Map result = underTest.loadProjectSettings(); -- 2.39.5