aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-webserver-auth/src
diff options
context:
space:
mode:
authorMatteo Mara <matteo.mara@sonarsource.com>2022-05-20 12:18:46 +0200
committersonartech <sonartech@sonarsource.com>2022-05-31 20:02:50 +0000
commit5dc735487ccaa4fd9557a2bc99b1654756c65d8d (patch)
tree163d8c5faf74096c50a87bc6247c0ffac76333e7 /server/sonar-webserver-auth/src
parentcc058a386950f69d40a6d422f2927c3f92857724 (diff)
downloadsonarqube-5dc735487ccaa4fd9557a2bc99b1654756c65d8d.tar.gz
sonarqube-5dc735487ccaa4fd9557a2bc99b1654756c65d8d.zip
SONAR-16260 improve project analysis when using project analysis token
Diffstat (limited to 'server/sonar-webserver-auth/src')
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/BasicAuthentication.java43
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/HttpHeadersAuthentication.java5
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/RequestAuthenticatorImpl.java44
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserAuthResult.java63
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/TokenUserSession.java76
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactory.java3
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/user/UserSessionFactoryImpl.java9
-rw-r--r--server/sonar-webserver-auth/src/main/java/org/sonar/server/usertoken/UserTokenAuthentication.java138
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/BasicAuthenticationTest.java62
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/RequestAuthenticatorImplTest.java50
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/user/TokenUserSessionTest.java170
-rw-r--r--server/sonar-webserver-auth/src/test/java/org/sonar/server/usertoken/UserTokenAuthenticationTest.java153
-rw-r--r--server/sonar-webserver-auth/src/testFixtures/java/org/sonar/server/user/TestUserSessionFactory.java6
13 files changed, 590 insertions, 232 deletions
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<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);
}
}
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<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());
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<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
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;
@@ -45,6 +46,14 @@ public class UserSessionFactoryImpl implements UserSessionFactory {
}
@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<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));
}
-
}
}
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<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")
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());
}
@@ -84,6 +90,21 @@ public class RequestAuthenticatorImplTest {
}
@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));
when(jwtHttpHandler.validateToken(request, response)).thenReturn(Optional.empty());
@@ -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<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)));
}
}
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;
@@ -46,6 +47,11 @@ public class TestUserSessionFactory implements UserSessionFactory {
}
@Override
+ public UserSession create(UserDto user, UserTokenDto userToken) {
+ return new TestUserSession(requireNonNull(user));
+ }
+
+ @Override
public UserSession createAnonymous() {
return new TestUserSession(null);
}