]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7208 WS user_tokens/generate a user can generate its own tokens
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 18 Jan 2016 08:29:39 +0000 (09:29 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 18 Jan 2016 17:35:42 +0000 (18:35 +0100)
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/TokenPermissionsValidator.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java

index 6affa614a70741a0e1a0d8fbf1eb324cfdc268ad..fe693a26ccceeb0ebd085f442c705a5a0346842c 100644 (file)
@@ -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. <br />" +
         "Please keep your tokens secret. They enable to authenticate and analyze projects.<br />" +
-        "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 (file)
index 0000000..8be1c2a
--- /dev/null
@@ -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();
+  }
+}
index e9bf3c1b5fdd5d72a0aab2c4be9d34083420cc2f..4bccd4fc0f611b4584cf259288a518b8bdad33c9 100644 (file)
@@ -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 {
index b72c3113c7fe440096f4843100c5280afd40dbaf..86d40f98edbf9622fdc92d81ccb4b7b75a0d11fa 100644 (file)
@@ -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();
   }
 
index dddc0dd296e7ab0eaf672b698f08ff7e0a0f25e5..d331da4852d876de4aa239cf6aeed35d66b18e83 100644 (file)
  */
 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;
   }