*/
package org.sonar.server.usertoken.ws;
+import java.util.Optional;
+import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.user.UserTokenDto;
import org.sonar.server.exceptions.ServerException;
import org.sonar.server.usertoken.TokenGenerator;
+import org.sonar.server.usertoken.TokenType;
import org.sonarqube.ws.UserTokens;
import org.sonarqube.ws.UserTokens.GenerateWsResponse;
+import static com.google.common.base.Preconditions.checkArgument;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+import static org.sonar.server.usertoken.TokenType.GLOBAL_ANALYSIS_TOKEN;
+import static org.sonar.server.usertoken.TokenType.PROJECT_ANALYSIS_TOKEN;
import static org.sonar.server.usertoken.TokenType.USER_TOKEN;
import static org.sonar.server.usertoken.ws.UserTokenSupport.ACTION_GENERATE;
import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN;
import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_NAME;
+import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_PROJECT_KEY;
+import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_TYPE;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
public class GenerateAction implements UserTokensWsAction {
.setMaximumLength(MAX_TOKEN_NAME_LENGTH)
.setDescription("Token name")
.setExampleValue("Project scan on Travis");
+
+ action.createParam(PARAM_TYPE)
+ .setSince("9.5")
+ .setDescription("Token Type. If this parameters is set to " + PROJECT_ANALYSIS_TOKEN.name() + ", it is necessary to provide the projectKey parameter too.")
+ .setPossibleValues(USER_TOKEN.name(), GLOBAL_ANALYSIS_TOKEN.name(), PROJECT_ANALYSIS_TOKEN.name())
+ .setDefaultValue(USER_TOKEN.name());
+
+ action.createParam(PARAM_PROJECT_KEY)
+ .setSince("9.5")
+ .setDescription("The key of the only project that can be analyzed by the " + PROJECT_ANALYSIS_TOKEN.name() + " being generated.");
}
@Override
UserDto user = userTokenSupport.getUser(dbSession, request);
checkTokenDoesNotAlreadyExists(dbSession, user, name);
- String token = tokenGenerator.generate(USER_TOKEN);
+ String token = generateToken(request, dbSession);
String tokenHash = hashToken(dbSession, token);
- UserTokenDto userTokenDto = insertTokenInDb(dbSession, user, name, tokenHash);
+ String projectKey = getProjecKeyFromRequest(request).orElse(null);
+ UserTokenDto userTokenDto = insertTokenInDb(dbSession, user, name, tokenHash, getTokenTypeFromRequest(request), projectKey);
return buildResponse(userTokenDto, token, user);
}
}
+ private String generateToken(Request request, DbSession dbSession) {
+ TokenType tokenType = getTokenTypeFromRequest(request);
+ validateParametersCombination(dbSession, request, tokenType);
+ return tokenGenerator.generate(tokenType);
+ }
+
+ private void validateParametersCombination(DbSession dbSession, Request request, TokenType tokenType) {
+ if (PROJECT_ANALYSIS_TOKEN.equals(tokenType)) {
+ validateProjectAnalysisParameters(dbSession, request);
+ } else if (GLOBAL_ANALYSIS_TOKEN.equals(tokenType)) {
+ validateGlobalAnalysisParameters(request);
+ }
+ }
+
+ private void validateProjectAnalysisParameters(DbSession dbSession, Request request) {
+ checkArgument(userTokenSupport.sameLoginAsConnectedUser(request), "A Project Analysis Token cannot be generated for another user.");
+ checkArgument(request.param(PARAM_PROJECT_KEY) != null, "A projectKey is needed when creating Project Analysis Token");
+ userTokenSupport.validateProjectScanPermission(dbSession, getProjecKeyFromRequest(request).orElse(""));
+ }
+
+ private void validateGlobalAnalysisParameters(Request request) {
+ checkArgument(userTokenSupport.sameLoginAsConnectedUser(request), "A Global Analysis Token cannot be generated for another user.");
+ userTokenSupport.validateGlobalScanPermission();
+ }
+
+ private static Optional<String> getProjecKeyFromRequest(Request request) {
+ String projectKey = null;
+ if (PROJECT_ANALYSIS_TOKEN.equals(getTokenTypeFromRequest(request))) {
+ projectKey = request.mandatoryParam(PARAM_PROJECT_KEY).trim();
+ }
+ return Optional.ofNullable(projectKey);
+ }
+
+ private static TokenType getTokenTypeFromRequest(Request request) {
+ String tokenTypeValue = request.mandatoryParam(PARAM_TYPE).trim();
+ return TokenType.valueOf(tokenTypeValue);
+ }
+
private String hashToken(DbSession dbSession, String token) {
String tokenHash = tokenGenerator.hash(token);
UserTokenDto userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash);
checkRequest(userTokenDto == null, "A user token for login '%s' and name '%s' already exists", user.getLogin(), name);
}
- private UserTokenDto insertTokenInDb(DbSession dbSession, UserDto user, String name, String tokenHash) {
+ private UserTokenDto insertTokenInDb(DbSession dbSession, UserDto user, String name, String tokenHash, TokenType tokenType, @Nullable String projectKey) {
UserTokenDto userTokenDto = new UserTokenDto()
.setUserUuid(user.getUuid())
.setName(name)
.setTokenHash(tokenHash)
.setCreatedAt(system.now())
- .setType(USER_TOKEN.name());
+ .setType(tokenType.name());
+
+ if (projectKey != null) {
+ userTokenDto.setProjectKey(projectKey);
+ }
+
dbClient.userTokenDao().insert(dbSession, userTokenDto, user.getLogin());
dbSession.commit();
return userTokenDto;
}
private static GenerateWsResponse buildResponse(UserTokenDto userTokenDto, String token, UserDto user) {
- return UserTokens.GenerateWsResponse.newBuilder()
+ GenerateWsResponse.Builder responseBuilder = GenerateWsResponse.newBuilder()
.setLogin(user.getLogin())
.setName(userTokenDto.getName())
.setCreatedAt(formatDateTime(userTokenDto.getCreatedAt()))
.setToken(token)
- .setType(userTokenDto.getType())
- .build();
+ .setType(userTokenDto.getType());
+
+ if (userTokenDto.getProjectKey() != null) {
+ responseBuilder.setProjectKey(userTokenDto.getProjectKey());
+ }
+
+ return responseBuilder.build();
}
}
*/
package org.sonar.server.usertoken.ws;
+import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
+import org.sonar.api.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.project.ProjectDto;
import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;
+import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
-import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
+import static org.sonar.db.permission.GlobalPermission.SCAN;
import static org.sonar.server.exceptions.NotFoundException.checkFound;
+import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
public class UserTokenSupport {
static final String ACTION_GENERATE = "generate";
static final String PARAM_LOGIN = "login";
static final String PARAM_NAME = "name";
+ static final String PARAM_TYPE = "type";
+ static final String PARAM_PROJECT_KEY = "projectKey";
private final DbClient dbClient;
private final UserSession userSession;
return user;
}
+ boolean sameLoginAsConnectedUser(Request request) {
+ return request.param(PARAM_LOGIN) == null || isLoggedInUser(userSession, request.param(PARAM_LOGIN));
+ }
+
private static void validate(UserSession userSession, @Nullable String requestLogin) {
userSession.checkLoggedIn();
if (userSession.isSystemAdministrator() || isLoggedInUser(userSession, requestLogin)) {
private static boolean isLoggedInUser(UserSession userSession, @Nullable String requestLogin) {
return requestLogin != null && requestLogin.equals(userSession.getLogin());
}
+
+ public void validateGlobalScanPermission() {
+ if (userSession.hasPermission(SCAN)){
+ return;
+ }
+ throw insufficientPrivilegesException();
+ }
+
+ public void validateProjectScanPermission(DbSession dbSession, String projecKeyFromRequest) {
+ Optional<ProjectDto> projectDto = dbClient.projectDao().selectProjectByKey(dbSession, projecKeyFromRequest);
+ if (projectDto.isEmpty()) {
+ throw new NotFoundException(format("Project key '%s' not found", projecKeyFromRequest));
+ }
+ validateProjectScanPermission(projectDto.get());
+ }
+
+ private void validateProjectScanPermission(ProjectDto projectDto) {
+ if (userSession.hasProjectPermission(UserRole.SCAN, projectDto) || userSession.hasPermission(SCAN)) {
+ return;
+ }
+ throw insufficientPrivilegesException();
+ }
}
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.db.permission.GlobalPermission.SCAN;
+import static org.sonar.server.usertoken.TokenType.GLOBAL_ANALYSIS_TOKEN;
+import static org.sonar.server.usertoken.TokenType.PROJECT_ANALYSIS_TOKEN;
+import static org.sonar.server.usertoken.TokenType.USER_TOKEN;
import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_LOGIN;
import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_NAME;
+import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_PROJECT_KEY;
+import static org.sonar.server.usertoken.ws.UserTokenSupport.PARAM_TYPE;
import static org.sonar.test.JsonAssert.assertJson;
public class GenerateActionTest {
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
- private TokenGenerator tokenGenerator = mock(TokenGenerator.class);
+ private final TokenGenerator tokenGenerator = mock(TokenGenerator.class);
- private WsActionTester ws = new WsActionTester(
+ private final WsActionTester ws = new WsActionTester(
new GenerateAction(db.getDbClient(), System2.INSTANCE, tokenGenerator, new UserTokenSupport(db.getDbClient(), userSession)));
@Before
public void setUp() {
- when(tokenGenerator.generate(TokenType.USER_TOKEN)).thenReturn("123456789");
+ when(tokenGenerator.generate(USER_TOKEN)).thenReturn("123456789");
+ when(tokenGenerator.generate(GLOBAL_ANALYSIS_TOKEN)).thenReturn("sqa_123456789");
+ when(tokenGenerator.generate(PROJECT_ANALYSIS_TOKEN)).thenReturn("sqp_123456789");
when(tokenGenerator.hash(anyString())).thenReturn("987654321");
}
assertThat(action.isPost()).isTrue();
assertThat(action.param("login").isRequired()).isFalse();
assertThat(action.param("name").isRequired()).isTrue();
+ assertThat(action.param("type").isRequired()).isFalse();
+ assertThat(action.param("type").since()).isEqualTo("9.5");
+ assertThat(action.param("projectKey").isRequired()).isFalse();
+ assertThat(action.param("projectKey").since()).isEqualTo("9.5");
+
}
@Test
public void json_example() {
UserDto user1 = db.users().insertUser(u -> u.setLogin("grace.hopper"));
- UserDto user2 = db.users().insertUser(u -> u.setLogin("ada.lovelace"));
logInAsSystemAdministrator();
String response = ws.newRequest()
assertThat(response.getCreatedAt()).isNotEmpty();
}
+ @Test
+ public void a_user_can_generate_globalAnalysisToken_with_the_global_scan_permission() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+ userSession.addPermission(SCAN);
+
+ GenerateWsResponse response = newRequest(null, TOKEN_NAME, GLOBAL_ANALYSIS_TOKEN, null);
+
+ assertThat(response.getLogin()).isEqualTo(user.getLogin());
+ assertThat(response.getToken()).startsWith("sqa_");
+ assertThat(response.getCreatedAt()).isNotEmpty();
+ }
+
+ @Test
+ public void a_user_can_generate_projectAnalysisToken_with_the_project_global_scan_permission() {
+ UserDto user = db.users().insertUser();
+ ComponentDto project = db.components().insertPublicProject();
+ userSession.logIn(user);
+ userSession.addPermission(SCAN);
+
+ GenerateWsResponse response = newRequest(null, TOKEN_NAME, PROJECT_ANALYSIS_TOKEN, project.getKey());
+
+ assertThat(response.getLogin()).isEqualTo(user.getLogin());
+ assertThat(response.getToken()).startsWith("sqp_");
+ assertThat(response.getProjectKey()).isEqualTo(project.getKey());
+ assertThat(response.getCreatedAt()).isNotEmpty();
+ }
+
+ @Test
+ public void a_user_can_generate_projectAnalysisToken_with_the_project_scan_permission() {
+ UserDto user = db.users().insertUser();
+ ComponentDto project = db.components().insertPublicProject();
+ userSession.logIn(user);
+ userSession.addProjectPermission(SCAN.toString(), project);
+
+ GenerateWsResponse response = newRequest(null, TOKEN_NAME, PROJECT_ANALYSIS_TOKEN, project.getKey());
+
+ assertThat(response.getLogin()).isEqualTo(user.getLogin());
+ assertThat(response.getToken()).startsWith("sqp_");
+ assertThat(response.getProjectKey()).isEqualTo(project.getKey());
+ assertThat(response.getCreatedAt()).isNotEmpty();
+ }
+
+ @Test
+ public void a_user_can_generate_projectAnalysisToken_with_the_project_scan_permission_passing_login() {
+ UserDto user = db.users().insertUser();
+ ComponentDto project = db.components().insertPublicProject();
+ userSession.logIn(user);
+ userSession.addProjectPermission(SCAN.toString(), project);
+
+ GenerateWsResponse responseWithLogin = newRequest(user.getLogin(), TOKEN_NAME, PROJECT_ANALYSIS_TOKEN, project.getKey());
+
+ assertThat(responseWithLogin.getLogin()).isEqualTo(user.getLogin());
+ assertThat(responseWithLogin.getToken()).startsWith("sqp_");
+ assertThat(responseWithLogin.getProjectKey()).isEqualTo(project.getKey());
+ assertThat(responseWithLogin.getCreatedAt()).isNotEmpty();
+ }
+
@Test
public void fail_if_login_does_not_exist() {
logInAsSystemAdministrator();
- assertThatThrownBy(() -> {
- newRequest("unknown-login", "any-name");
- })
+ assertThatThrownBy(() -> newRequest("unknown-login", "any-name"))
.isInstanceOf(NotFoundException.class)
.hasMessage("User with login 'unknown-login' doesn't exist");
}
UserDto user = db.users().insertUser();
logInAsSystemAdministrator();
+ String login = user.getLogin();
+
+ assertThatThrownBy(() -> newRequest(login, " "))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("The 'name' parameter is missing");
+ }
+
+ @Test
+ public void fail_if_globalAnalysisToken_created_for_other_user() {
+ UserDto user = db.users().insertUser();
+ String login = user.getLogin();
+ logInAsSystemAdministrator();
+
+ assertThatThrownBy(() -> newRequest(login, "token 1", GLOBAL_ANALYSIS_TOKEN, null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("A Global Analysis Token cannot be generated for another user.");
+ }
+
+ @Test
+ public void fail_if_projectAnalysisToken_created_for_other_user() {
+ UserDto user = db.users().insertUser();
+ String login = user.getLogin();
+ logInAsSystemAdministrator();
+
+ assertThatThrownBy(() -> newRequest(login, "token 1", PROJECT_ANALYSIS_TOKEN, null))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("A Project Analysis Token cannot be generated for another user.");
+ }
+
+ @Test
+ public void fail_if_globalAnalysisToken_created_without_global_permission() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
assertThatThrownBy(() -> {
- newRequest(user.getLogin(), " ");
+ newRequest(null, "token 1", GLOBAL_ANALYSIS_TOKEN, null);
+ })
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessage("Insufficient privileges");
+ }
+
+ @Test
+ public void fail_if_projectAnalysisToken_created_without_project_permission() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+ String projectKey = db.components().insertPublicProject().getKey();
+
+ assertThatThrownBy(() -> newRequest(null, "token 1", PROJECT_ANALYSIS_TOKEN, projectKey))
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessage("Insufficient privileges");
+ }
+
+ @Test
+ public void fail_if_projectAnalysisToken_created_for_blank_projectKey() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+
+ assertThatThrownBy(() -> {
+ newRequest(null, "token 1", PROJECT_ANALYSIS_TOKEN, null);
})
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("The 'name' parameter is missing");
+ .hasMessage("A projectKey is needed when creating Project Analysis Token");
+ }
+
+ @Test
+ public void fail_if_projectAnalysisToken_created_for_non_existing_project() {
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user);
+ userSession.addPermission(SCAN);
+
+ assertThatThrownBy(() -> {
+ newRequest(null, "token 1", PROJECT_ANALYSIS_TOKEN, "nonExistingProjectKey");
+ })
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Project key 'nonExistingProjectKey' not found");
}
@Test
public void fail_if_token_with_same_login_and_name_exists() {
UserDto user = db.users().insertUser();
+ String login = user.getLogin();
logInAsSystemAdministrator();
db.users().insertToken(user, t -> t.setName(TOKEN_NAME));
assertThatThrownBy(() -> {
- newRequest(user.getLogin(), TOKEN_NAME);
+ newRequest(login, TOKEN_NAME);
})
.isInstanceOf(BadRequestException.class)
.hasMessage(String.format("A user token for login '%s' and name 'Third Party Application' already exists", user.getLogin()));
@Test
public void fail_if_token_hash_already_exists_in_db() {
UserDto user = db.users().insertUser();
+ String login = user.getLogin();
logInAsSystemAdministrator();
when(tokenGenerator.hash(anyString())).thenReturn("987654321");
db.users().insertToken(user, t -> t.setTokenHash("987654321"));
assertThatThrownBy(() -> {
- newRequest(user.getLogin(), TOKEN_NAME);
+ newRequest(login, TOKEN_NAME);
})
.isInstanceOf(ServerException.class)
.hasMessage("Error while generating token. Please try again.");
@Test
public void throw_ForbiddenException_if_non_administrator_creates_token_for_someone_else() {
- UserDto user = db.users().insertUser();
+ String login = db.users().insertUser().getLogin();
userSession.logIn().setNonSystemAdministrator();
assertThatThrownBy(() -> {
- newRequest(user.getLogin(), TOKEN_NAME);
+ newRequest(login, TOKEN_NAME);
})
.isInstanceOf(ForbiddenException.class);
}
@Test
public void throw_UnauthorizedException_if_not_logged_in() {
- UserDto user = db.users().insertUser();
+ String login = db.users().insertUser().getLogin();
userSession.anonymous();
assertThatThrownBy(() -> {
- newRequest(user.getLogin(), TOKEN_NAME);
+ newRequest(login, TOKEN_NAME);
})
.isInstanceOf(UnauthorizedException.class);
}
return testRequest.executeProtobuf(GenerateWsResponse.class);
}
+ private GenerateWsResponse newRequest(@Nullable String login, String name, TokenType tokenType, @Nullable String projectKey) {
+ TestRequest testRequest = ws.newRequest()
+ .setParam(PARAM_NAME, name)
+ .setParam(PARAM_TYPE, tokenType.toString());
+ if (login != null) {
+ testRequest.setParam(PARAM_LOGIN, login);
+ }
+ if (projectKey != null) {
+ testRequest.setParam(PARAM_PROJECT_KEY, projectKey);
+ }
+
+ return testRequest.executeProtobuf(GenerateWsResponse.class);
+ }
+
private void logInAsSystemAdministrator() {
userSession.logIn().setSystemAdministrator();
}