From 0742be3f903c7b3a2381dfd283cd3855af1ce625 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Mon, 18 Jan 2016 09:29:39 +0100 Subject: [PATCH] SONAR-7208 WS user_tokens/generate a user can generate its own tokens --- .../server/usertoken/ws/GenerateAction.java | 28 ++++++++----- .../ws/TokenPermissionsValidator.java | 40 +++++++++++++++++++ .../usertoken/ws/GenerateActionTest.java | 39 ++++++++++++++---- .../server/usertoken/ws/UserTokensWsTest.java | 2 +- .../client/usertoken/GenerateWsRequest.java | 6 ++- 5 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java index 6affa614a70..fe693a26ccc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java @@ -24,9 +24,9 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; -import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.user.UserDto; import org.sonar.db.user.UserTokenDto; import org.sonar.server.exceptions.ServerException; import org.sonar.server.user.UserSession; @@ -36,7 +36,7 @@ import org.sonarqube.ws.WsUserTokens.GenerateWsResponse; import org.sonarqube.ws.client.usertoken.GenerateWsRequest; import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; -import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; import static org.sonar.server.ws.WsUtils.checkRequest; import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_GENERATE; @@ -63,13 +63,12 @@ public class GenerateAction implements UserTokensWsAction { .setPost(true) .setDescription("Generate a user access token.
" + "Please keep your tokens secret. They enable to authenticate and analyze projects.
" + - "It requires administration permissions.") + "If the login is set, it requires administration permissions. Otherwise, a token is generated for the authenticated user.") .setResponseExample(getClass().getResource("generate-example.json")) .setHandler(this); action.createParam(PARAM_LOGIN) - .setRequired(true) - .setDescription("User login") + .setDescription("User login. If not set, the token is generated for the authenticated user.") .setExampleValue("g.hopper"); action.createParam(PARAM_NAME) @@ -85,11 +84,10 @@ public class GenerateAction implements UserTokensWsAction { } private WsUserTokens.GenerateWsResponse doHandle(GenerateWsRequest request) { - userSession.checkPermission(GlobalPermissions.SYSTEM_ADMIN); - DbSession dbSession = dbClient.openSession(false); try { checkWsRequest(dbSession, request); + TokenPermissionsValidator.validate(userSession, request.getLogin()); String token = tokenGenerator.generate(); String tokenHash = hashToken(dbSession, token); @@ -120,7 +118,10 @@ public class GenerateAction implements UserTokensWsAction { } private void checkLoginExists(DbSession dbSession, GenerateWsRequest request) { - checkFound(dbClient.userDao().selectByLogin(dbSession, request.getLogin()), "User with login '%s' not found", request.getLogin()); + UserDto user = dbClient.userDao().selectByLogin(dbSession, request.getLogin()); + if (user == null) { + throw insufficientPrivilegesException(); + } } private UserTokenDto insertTokenInDb(DbSession dbSession, GenerateWsRequest request, String tokenHash) { @@ -135,10 +136,15 @@ public class GenerateAction implements UserTokensWsAction { return userTokenDto; } - private static GenerateWsRequest toCreateWsRequest(Request request) { - return new GenerateWsRequest() - .setLogin(request.mandatoryParam(PARAM_LOGIN)) + private GenerateWsRequest toCreateWsRequest(Request request) { + GenerateWsRequest generateWsRequest = new GenerateWsRequest() + .setLogin(request.param(PARAM_LOGIN)) .setName(request.mandatoryParam(PARAM_NAME)); + if (generateWsRequest.getLogin() == null) { + generateWsRequest.setLogin(userSession.getLogin()); + } + + return generateWsRequest; } private static GenerateWsResponse buildResponse(UserTokenDto userTokenDto, String token) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java new file mode 100644 index 00000000000..8be1c2a32e3 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.usertoken.ws; + +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.server.user.UserSession; + +import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException; + +class TokenPermissionsValidator { + private TokenPermissionsValidator() { + // prevent instantiation + } + + static void validate(UserSession userSession, String requestLogin) { + if (userSession.hasPermission(GlobalPermissions.SYSTEM_ADMIN) + || requestLogin.equals(userSession.getLogin())) { + return; + } + + throw insufficientPrivilegesException(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java index e9bf3c1b5fd..4bccd4fc0f6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java @@ -21,6 +21,7 @@ package org.sonar.server.usertoken.ws; import java.io.IOException; import java.io.InputStream; +import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -31,16 +32,18 @@ import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbTester; import org.sonar.db.user.UserDbTester; import org.sonar.server.exceptions.BadRequestException; -import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ServerException; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.usertoken.TokenGenerator; +import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; import org.sonar.test.DbTests; import org.sonarqube.ws.MediaTypes; import org.sonarqube.ws.WsUserTokens.GenerateWsResponse; import static com.google.common.base.Throwables.propagate; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -82,7 +85,7 @@ public class GenerateActionTest { } @Test - public void return_json_example() { + public void json_example() { String response = ws.newRequest() .setMediaType(MediaTypes.JSON) .setParam(PARAM_LOGIN, GRACE_HOPPER) @@ -92,10 +95,18 @@ public class GenerateActionTest { assertJson(response).isSimilarTo(getClass().getResource("generate-example.json")); } + @Test + public void a_user_can_generate_token_for_himself() { + userSession.login(GRACE_HOPPER).setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + + GenerateWsResponse response = newRequest(null, TOKEN_NAME); + + assertThat(response.getLogin()).isEqualTo(GRACE_HOPPER); + } + @Test public void fail_if_login_does_not_exist() { - expectedException.expect(NotFoundException.class); - expectedException.expectMessage("User with login 'unknown-login' not found"); + expectedException.expect(ForbiddenException.class); newRequest("unknown-login", "any-name"); } @@ -120,11 +131,23 @@ public class GenerateActionTest { newRequest(GRACE_HOPPER, TOKEN_NAME); } - private GenerateWsResponse newRequest(String login, String name) { - InputStream responseStream = ws.newRequest() + @Test + public void fail_if_insufficient_privileges() { + userSession.login(ADA_LOVELACE).setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + expectedException.expect(ForbiddenException.class); + + newRequest(GRACE_HOPPER, TOKEN_NAME); + } + + private GenerateWsResponse newRequest(@Nullable String login, String name) { + TestRequest testRequest = ws.newRequest() .setMediaType(MediaTypes.PROTOBUF) - .setParam(PARAM_LOGIN, login) - .setParam(PARAM_NAME, name) + .setParam(PARAM_NAME, name); + if (login != null) { + testRequest.setParam(PARAM_LOGIN, login); + } + + InputStream responseStream = testRequest .execute().getInputStream(); try { diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java index b72c3113c7f..86d40f98edb 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java @@ -57,7 +57,7 @@ public class UserTokensWsTest { assertThat(action.since()).isEqualTo("5.3"); assertThat(action.responseExampleAsString()).isNotEmpty(); assertThat(action.isPost()).isTrue(); - assertThat(action.param("login").isRequired()).isTrue(); + assertThat(action.param("login").isRequired()).isFalse(); assertThat(action.param("name").isRequired()).isTrue(); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java index dddc0dd296e..d331da4852d 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java @@ -19,15 +19,19 @@ */ package org.sonarqube.ws.client.usertoken; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + public class GenerateWsRequest { private String login; private String name; + @CheckForNull public String getLogin() { return login; } - public GenerateWsRequest setLogin(String login) { + public GenerateWsRequest setLogin(@Nullable String login) { this.login = login; return this; } -- 2.39.5