]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16260 improve project analysis when using project analysis token
authorMatteo Mara <matteo.mara@sonarsource.com>
Fri, 20 May 2022 10:18:46 +0000 (12:18 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 31 May 2022 20:02:50 +0000 (20:02 +0000)
28 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserTokenDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserTokenMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTokenDaoTest.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/BasicAuthentication.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserAuthResult.java [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactory.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java [new file with mode: 0644]
server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java
server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClient.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/WsTestUtil.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/cache/DefaultAnalysisCacheLoaderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/qualitygate/QualityGateCheckTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ReportPublisherTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/DefaultProjectRepositoriesLoaderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultGlobalSettingsLoaderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/repository/settings/DefaultProjectSettingsLoaderTest.java

index c3630bbc7ea5ba6742142ea0c4ce12d5ce98d137..9e1d2df4b25c359770c0c3242bb29c296054a340 100644 (file)
@@ -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;
+  }
 }
index ba43d789d86290c1ff76a022b9f0bb7bafc7188f..0536971d2d75afbc4813cd4f03e24d4f6f31a3eb 100644 (file)
@@ -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"
   </sql>
 
   <insert id="insert" parameterType="UserToken" useGeneratedKeys="false">
index 2f5dc9c7d892d7618130d70ca186b67214fcf91e..3c142fe13d05a9e6b8524b5319772054aca89906 100644 (file)
@@ -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());
index b5ac6571cba35fe504c3f548c0e9536c55eac7ac..b95eab152b9f92530762dc3315fe4e0bf2f56e96 100644 (file)
@@ -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<UserDto> authenticate(HttpServletRequest request) {
     return extractCredentialsFromHeader(request)
-      .flatMap(credentials -> Optional.of(authenticate(credentials, request)));
+      .flatMap(credentials -> Optional.ofNullable(authenticate(credentials, request)));
   }
 
   public static Optional<Credentials> 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> 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);
   }
 
 }
index 1562bd7e3b384131f5a53ed95bc8782354b61a95..f24e9d721ce830ca18d0bf06ffab96fa8d737c0f 100644 (file)
@@ -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<UserDto> getUserFromToken(HttpServletRequest request, HttpServletResponse response) {
     Optional<JwtHttpHandler.Token> token = jwtHttpHandler.getToken(request, response);
-    if (!token.isPresent()) {
+    if (token.isEmpty()) {
       return Optional.empty();
     }
     Date now = new Date(system2.now());
index 4f7871f594a7a25edf0684aaa1374d39401224cb..ade1d51f9050e8b88b870b0cf1b8672ca3a3e031 100644 (file)
@@ -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<CustomAuthentication> 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<UserDto> 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<UserDto> 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<UserDto> 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> 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 (file)
index 0000000..7f5cafc
--- /dev/null
@@ -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 (file)
index 0000000..7f0b730
--- /dev/null
@@ -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());
+    }
+  }
+
+}
index 1995b8cfbee4e9f9822f5e774c200754a18cee53..c7cf98fccd7bfa0886d6edd8c6ba6a578fe4fc3c 100644 (file)
@@ -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();
 
 }
index 3e049d4ae2375d375ec2a50bc011f8bf8c52f43c..ca32c8afd51cbdaa4db44536b4fde8d31685f4fe 100644 (file)
@@ -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);
index 77eba8c2defbad0a7fa07fbfeed0bd28a0c92615..be066b6a422db6847f0d95d57ce98fe96a70b9fb 100644 (file)
  */
 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<String> 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<TokenType, Set<String>> 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<UserAuthResult> authenticate(HttpServletRequest request) {
+    if (isTokenBasedAuthentication(request)) {
+      Optional<Credentials> 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<Credentials> credentialsOptional = extractCredentialsFromHeader(request);
+    return credentialsOptional.map(credentials -> credentials.getPassword().isEmpty()).orElse(false);
   }
 
-  private static boolean shouldBeAbleToAccessEndpoint(TokenType tokenType, String requestedEndpoint) {
-    Set<String> 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));
     }
-
   }
 }
index 05c2231f4c7265ec5b47cdd4f917ab9284b74a71..01cde7bb26417becd4d91e83512d0becc1926817 100644 (file)
@@ -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<UserDto> 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")
index 2f79f6028ca665b7a19ba6307d756fb5cac96fae..9fa95233e5515cf34c6562f9d3b3cc3e0f36e75b 100644 (file)
@@ -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 (file)
index 0000000..6ffe52c
--- /dev/null
@@ -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;
+  }
+
+}
index 32b2c3948ba3706f48cc553d4d82550172d8c1bf..3e353f20e286563a6bd605b1b4cc2b0e0e95766d 100644 (file)
  */
 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<UserAuthResult> 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<UserAuthResult> 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)));
   }
 }
index d09d9ea662d240b49b212a0d229e43f7865f7cb1..92d1d0567819b7a6d8a5ebd01a8820917de5bc65 100644 (file)
@@ -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);
index a554aa5c2383626c0cf43de53214a20bac7c88b9..8dc5dc3a39f21f319d9e45c11342c2d0aa5f1f05 100644 (file)
@@ -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();
   }
index bd8452f4ad1a6ec062b1ba44d19f4b5606da370e..a11326008edc1edc9d98ec45a729cf705551773a 100644 (file)
  */
 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();
 
-
 }
index 606785aaf8a04a7cb3999845fd78a29a2ff4cc2d..8e86aa8b0b4682b58675e9811dcc6ab5636c5794 100644 (file)
@@ -58,6 +58,6 @@ public class ScannerWsClientProvider {
     }
 
     return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null,
-      globalMode, scannerProps);
+      globalMode);
   }
 }
index 157c0745b5eab383914c80adfb4034c892efb90d..45905619f03046dee6e310fd433a8f6e34800fa4 100644 (file)
@@ -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<WsRequest> {
index 20dc164f0757c628115e8e7a80b1babd763f95e6..752b6ef0891c2f56418e04866fbe43d607a63c25 100644 (file)
@@ -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");
-  }
 }
index 0c726b4035a382005a0faf53470568a95b4ed05c..d2b7cd4840d3d9721928e09db8c062cdb3d769e7 100644 (file)
@@ -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();
index be8ad554eca890d9e058e0d4ee9c6a0d2415f73c..3281d457a7dcdab1661f0e1fb27c4f48083a883e 100644 (file)
@@ -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<GetRequest> requestCaptor = ArgumentCaptor.forClass(GetRequest.class);
+    ArgumentCaptor<WsRequest> requestCaptor = ArgumentCaptor.forClass(WsRequest.class);
     verify(wsClient).call(requestCaptor.capture());
     assertThat(requestCaptor.getValue().getPath()).isEqualTo(expectedPath);
   }
index 89613f6bba00f0610799fc6a501fdabef795d372..bd3803a7eb1aae2916b3c5646995e0aa8ae3556b 100644 (file)
@@ -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<GetRequest> {
+  private static class WsRequestPathMatcher implements ArgumentMatcher<WsRequest> {
     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());
     }
   }
index 55d6772e37ef76cb0f8585432ad301f46c2eb8c6..cc440601a2f72b3fff12040afdcd9a835a85c5e5 100644 (file)
@@ -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<PostRequest> capture = ArgumentCaptor.forClass(PostRequest.class);
+    ArgumentCaptor<WsRequest> 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<PostRequest> capture = ArgumentCaptor.forClass(PostRequest.class);
+    ArgumentCaptor<WsRequest> 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<PostRequest> capture = ArgumentCaptor.forClass(PostRequest.class);
+    ArgumentCaptor<WsRequest> capture = ArgumentCaptor.forClass(WsRequest.class);
     verify(wsClient).call(capture.capture());
 
     WsRequest wsRequest = capture.getValue();
index ca09e2bd359f3f81804aa72ee8f11760139a09c4..829095bc81bfc515d84ffe89a414262e39ea94b5 100644 (file)
@@ -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();
   }
index daae92c11c21c931cc9fb0e2f60bcddeaadbc4ac..069d7a85fb2a7ed2366d3afba4af60503bf2434b 100644 (file)
@@ -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<String, String> result = underTest.loadGlobalSettings();
 
index 9699b433258a6c5a6829b04201b66796c1aa622f..ae6e98cd7ac00dcdd99a237d8e81a3a56cda5a23 100644 (file)
@@ -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<String, String> result = underTest.loadProjectSettings();