]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5934 Simple User Creation WS
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 16 Dec 2014 17:40:08 +0000 (18:40 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 16 Dec 2014 17:40:13 +0000 (18:40 +0100)
12 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/user/UserService.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexer.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserResultSetIterator.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWs.java
server/sonar-server/src/test/java/org/sonar/server/user/UserServiceMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexerTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
server/sonar-server/src/test/resources/org/sonar/server/user/index/UserIndexTest/get_nullable_by_login.json [new file with mode: 0644]

index ef0bdb16177d0a8f60e655b9f44652b8eb6867c4..26fb763fce5a123f13130c97f96049bf7b302ba6 100644 (file)
@@ -184,6 +184,7 @@ import org.sonar.server.user.*;
 import org.sonar.server.user.db.GroupDao;
 import org.sonar.server.user.db.UserDao;
 import org.sonar.server.user.db.UserGroupDao;
+import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.user.ws.FavoritesWs;
@@ -468,10 +469,12 @@ class ServerComponents {
     pico.addSingleton(DefaultUserFinder.class);
     pico.addSingleton(DefaultUserService.class);
     pico.addSingleton(UsersWs.class);
+    pico.addSingleton(org.sonar.server.user.ws.CreateAction.class);
     pico.addSingleton(FavoritesWs.class);
     pico.addSingleton(UserPropertiesWs.class);
     pico.addSingleton(UserIndexDefinition.class);
     pico.addSingleton(UserIndexer.class);
+    pico.addSingleton(UserIndex.class);
     pico.addSingleton(UserService.class);
     pico.addSingleton(UserCreator.class);
 
index e7f504f64a2d6b88b4946ed7f4d3e29ab1f74ef2..9f6945b7a10baa8431dbee9a22a10432638e7e1c 100644 (file)
@@ -22,15 +22,21 @@ package org.sonar.server.user;
 
 import org.sonar.api.ServerComponent;
 import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.user.index.UserIndexer;
 
+import javax.annotation.CheckForNull;
+
 public class UserService implements ServerComponent {
 
   private final UserIndexer userIndexer;
+  private final UserIndex userIndex;
   private final UserCreator userCreator;
 
-  public UserService(UserIndexer userIndexer, UserCreator userCreator) {
+  public UserService(UserIndexer userIndexer, UserIndex userIndex, UserCreator userCreator) {
     this.userIndexer = userIndexer;
+    this.userIndex = userIndex;
     this.userCreator = userCreator;
   }
 
@@ -40,6 +46,16 @@ public class UserService implements ServerComponent {
     userIndexer.index();
   }
 
+  @CheckForNull
+  public UserDoc getByLogin(String login) {
+    return userIndex.getByLogin(login);
+  }
+
+  @CheckForNull
+  public UserDoc getNullableByLogin(String login) {
+    return userIndex.getNullableByLogin(login);
+  }
+
   public void index() {
     userIndexer.index();
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java b/server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java
new file mode 100644 (file)
index 0000000..c9beb4b
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.index;
+
+import org.elasticsearch.action.get.GetRequestBuilder;
+import org.elasticsearch.action.get.GetResponse;
+import org.sonar.api.ServerComponent;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.exceptions.NotFoundException;
+
+import javax.annotation.CheckForNull;
+
+public class UserIndex implements ServerComponent {
+
+  private final EsClient esClient;
+
+  public UserIndex(EsClient esClient) {
+    this.esClient = esClient;
+  }
+
+  @CheckForNull
+  public UserDoc getNullableByLogin(String login) {
+    GetRequestBuilder request = esClient.prepareGet(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, login)
+      .setFetchSource(true)
+      .setRouting(login);
+    GetResponse response = request.get();
+    if (response.isExists()) {
+      return new UserDoc(response.getSource());
+    }
+    return null;
+  }
+
+  public UserDoc getByLogin(String login) {
+    UserDoc userDoc = getNullableByLogin(login);
+    if (userDoc == null) {
+      throw new NotFoundException(String.format("User '%s' not found", login));
+    }
+    return userDoc;
+  }
+
+}
index 611b776a1ee776959f3840739eb676a0a9bd0561..94bc1e1f317ae6cb5b7a6d4ecc24b6f013a1afe0 100644 (file)
@@ -21,6 +21,8 @@
 package org.sonar.server.user.index;
 
 import org.apache.commons.dbutils.DbUtils;
+import org.elasticsearch.action.get.GetRequestBuilder;
+import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.action.update.UpdateRequest;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.server.db.DbClient;
@@ -28,6 +30,8 @@ import org.sonar.server.es.BaseIndexer;
 import org.sonar.server.es.BulkIndexer;
 import org.sonar.server.es.EsClient;
 
+import javax.annotation.CheckForNull;
+
 import java.sql.Connection;
 import java.util.Iterator;
 
@@ -71,6 +75,21 @@ public class UserIndexer extends BaseIndexer {
     return maxUpdatedAt;
   }
 
+  @CheckForNull
+  public UserDoc getNullableByKey(String login) {
+    GetRequestBuilder request = esClient.prepareGet()
+      .setType(UserIndexDefinition.INDEX)
+      .setIndex(UserIndexDefinition.TYPE_USER)
+      .setId(login)
+      .setFetchSource(true)
+      .setRouting(login);
+    GetResponse response = request.get();
+    if (response.isExists()) {
+      return new UserDoc(response.getSource());
+    }
+    return null;
+  }
+
   private UpdateRequest newUpsertRequest(UserDoc user) {
     return new UpdateRequest(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, user.login())
       .doc(user.getFields())
index b621e87ec9b119cd93b27d83dcb30a57063402ac..352607553c61d5c2a9c49f1cc86a3471a511aab9 100644 (file)
@@ -36,7 +36,6 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.Iterator;
 import java.util.List;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -106,8 +105,8 @@ class UserResultSetIterator extends ResultSetIterator<UserDoc> {
       reader = new StringReader(csv);
       csvParser = new CSVParser(reader, CSVFormat.DEFAULT);
       for (CSVRecord csvRecord : csvParser) {
-        for (Iterator<String> iter = csvRecord.iterator(); iter.hasNext();) {
-          result.add(iter.next());
+        for (String aCsvRecord : csvRecord) {
+          result.add(aCsvRecord);
         }
       }
       return result;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
new file mode 100644 (file)
index 0000000..aa0bd94
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.user.NewUser;
+import org.sonar.server.user.ReactivationException;
+import org.sonar.server.user.UserService;
+
+public class CreateAction implements RequestHandler {
+
+  private static final String PARAM_LOGIN = "login";
+  private static final String PARAM_PASSWORD = "password";
+  private static final String PARAM_PASSWORD_CONFIRMATION = "password_confirmation";
+  private static final String PARAM_NAME = "name";
+  private static final String PARAM_EMAIL = "email";
+  private static final String PARAM_PREVENT_REACTIVATION = "prevent_reactivation";
+
+  private final UserService userService;
+
+  public CreateAction(UserService userService) {
+    this.userService = userService;
+  }
+
+  void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction("create")
+      .setDescription("Create a user. Requires Administer System permission")
+      .setSince("3.7")
+      .setPost(true)
+      .setHandler(this);
+
+    action.createParam(PARAM_LOGIN)
+      .setDescription("User login")
+      .setRequired(true)
+      .setExampleValue("myuser");
+
+    action.createParam(PARAM_PASSWORD)
+      .setDescription("User password")
+      .setRequired(true)
+      .setExampleValue("mypassword");
+
+    action.createParam(PARAM_PASSWORD_CONFIRMATION)
+      .setDescription("Must be the same value as \"password\"")
+      .setRequired(true)
+      .setExampleValue("mypassword");
+
+    action.createParam(PARAM_NAME)
+      .setDescription("User name")
+      .setRequired(true)
+      .setExampleValue("My Name");
+
+    action.createParam(PARAM_EMAIL)
+      .setDescription("User email")
+      .setExampleValue("myname@email.com");
+
+    action.createParam(PARAM_PREVENT_REACTIVATION)
+      .setDescription("If set to true and if the user has been removed, a status 409 will be returned")
+      .setDefaultValue(false)
+      .setBooleanPossibleValues();
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    NewUser newUser = NewUser.create()
+      .setLogin(request.mandatoryParam(PARAM_LOGIN))
+      .setName(request.mandatoryParam(PARAM_NAME))
+      .setEmail(request.param(PARAM_EMAIL))
+      .setPassword(request.mandatoryParam(PARAM_PASSWORD))
+      .setPasswordConfirmation(request.mandatoryParam(PARAM_PASSWORD_CONFIRMATION))
+      .setPreventReactivation(request.mandatoryParamAsBoolean(PARAM_PREVENT_REACTIVATION));
+
+    try {
+      userService.create(newUser);
+    } catch (ReactivationException e) {
+      // write409(response, e.ruleKey());
+    }
+  }
+
+  // private void writeResponse(Response response, RuleKey ruleKey) {
+  // Rule rule = service.getNonNullByKey(ruleKey);
+  // JsonWriter json = response.newJsonWriter().beginObject().name("rule");
+  // mapping.write(rule, json, null /* TODO replace by SearchOptions immutable constant */);
+  // json.endObject().close();
+  // }
+  //
+  // private void write409(Response response, RuleKey ruleKey) {
+  // Rule rule = service.getNonNullByKey(ruleKey);
+  //
+  // Response.Stream stream = response.stream();
+  // stream.setStatus(409);
+  // stream.setMediaType(MimeTypes.JSON);
+  // JsonWriter json = JsonWriter.of(new OutputStreamWriter(stream.output())).beginObject().name("rule");
+  // mapping.write(rule, json, null /* TODO replace by SearchOptions immutable constant */);
+  // json.endObject().close();
+  // }
+}
index 518a39f5987c7e5c1cf0c9c7e2b05a40f0f2502c..a5b7d1abdf2d364dcdd0ed9412d68624adfbbe98 100644 (file)
@@ -26,6 +26,12 @@ import org.sonar.api.server.ws.WebService;
 
 public class UsersWs implements WebService {
 
+  private final CreateAction createAction;
+
+  public UsersWs(CreateAction createAction) {
+    this.createAction = createAction;
+  }
+
   @Override
   public void define(Context context) {
     NewController controller = context.createController("api/users")
@@ -33,7 +39,7 @@ public class UsersWs implements WebService {
       .setDescription("Users management");
 
     defineSearchAction(controller);
-    defineCreateAction(controller);
+    createAction.define(controller);
     defineUpdateAction(controller);
     defineDeactivateAction(controller);
 
@@ -59,40 +65,6 @@ public class UsersWs implements WebService {
     RailsHandler.addFormatParam(action);
   }
 
-  private void defineCreateAction(NewController controller) {
-    NewAction action = controller.createAction("create")
-      .setDescription("Create a user. Requires Administer System permission")
-      .setSince("3.7")
-      .setPost(true)
-      .setHandler(RailsHandler.INSTANCE);
-
-    action.createParam("login")
-      .setDescription("User login")
-      .setRequired(true)
-      .setExampleValue("myuser");
-
-    action.createParam("password")
-      .setDescription("User password")
-      .setRequired(true)
-      .setExampleValue("mypassword");
-
-    action.createParam("password_confirmation")
-      .setDescription("Must be the same value as \"password\"")
-      .setRequired(true)
-      .setExampleValue("mypassword");
-
-    action.createParam("name")
-      .setDescription("User name")
-      .setRequired(true)
-      .setExampleValue("My Name");
-
-    action.createParam("email")
-      .setDescription("User email")
-      .setExampleValue("myname@email.com");
-
-    RailsHandler.addFormatParam(action);
-  }
-
   private void defineUpdateAction(NewController controller) {
     NewAction action = controller.createAction("update")
       .setDescription("Update a user. Requires Administer System permission")
index c7a61cc6d92e0336e9f557b55df7543cc5efa281..164a45f8439fcf850e05951497cab9a5c39dc19b 100644 (file)
@@ -96,6 +96,42 @@ public class UserServiceMediumTest {
       .setScmAccounts(newArrayList("u1", "u_1")));
   }
 
+  @Test
+  public void get_nullable_by_login() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    GroupDto userGroup = new GroupDto().setName(CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE);
+    dbClient.groupDao().insert(session, userGroup);
+    session.commit();
+
+    service.create(NewUser.create()
+      .setLogin("user")
+      .setName("User")
+      .setEmail("user@mail.com")
+      .setPassword("password")
+      .setPasswordConfirmation("password")
+      .setScmAccounts(newArrayList("u1", "u_1")));
+
+    assertThat(service.getNullableByLogin("user")).isNotNull();
+  }
+
+  @Test
+  public void get_by_login() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    GroupDto userGroup = new GroupDto().setName(CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE);
+    dbClient.groupDao().insert(session, userGroup);
+    session.commit();
+
+    service.create(NewUser.create()
+      .setLogin("user")
+      .setName("User")
+      .setEmail("user@mail.com")
+      .setPassword("password")
+      .setPasswordConfirmation("password")
+      .setScmAccounts(newArrayList("u1", "u_1")));
+
+    assertThat(service.getByLogin("user")).isNotNull();
+  }
+
   @Test
   public void index() throws Exception {
     UserDto userDto = new UserDto().setLogin("user").setEmail("user@mail.com").setCreatedAt(System.currentTimeMillis()).setUpdatedAt(System.currentTimeMillis());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexTest.java
new file mode 100644 (file)
index 0000000..8172bba
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.index;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Settings;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.NotFoundException;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class UserIndexTest {
+
+  @Rule
+  public EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
+
+  private UserIndex index;
+
+  @Before
+  public void setUp() {
+    index = new UserIndex(esTester.client());
+  }
+
+  @Test
+  public void get_nullable_by_login() throws Exception {
+    esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, this.getClass(), "get_nullable_by_login.json");
+
+    UserDoc userDoc = index.getNullableByLogin("user1");
+    assertThat(userDoc).isNotNull();
+    assertThat(userDoc.login()).isEqualTo("user1");
+    assertThat(userDoc.name()).isEqualTo("User1");
+    assertThat(userDoc.email()).isEqualTo("user1@mail.com");
+    assertThat(userDoc.active()).isTrue();
+    assertThat(userDoc.scmAccounts()).containsOnly("user_1", "u1");
+    assertThat(userDoc.createdAt()).isEqualTo(1500000000000L);
+    assertThat(userDoc.updatedAt()).isEqualTo(1500000000000L);
+
+    assertThat(index.getNullableByLogin("unknown")).isNull();
+  }
+
+  @Test
+  public void get_by_login() throws Exception {
+    esTester.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, this.getClass(), "get_nullable_by_login.json");
+
+    UserDoc userDoc = index.getByLogin("user1");
+    assertThat(userDoc).isNotNull();
+    assertThat(userDoc.login()).isEqualTo("user1");
+    assertThat(userDoc.name()).isEqualTo("User1");
+    assertThat(userDoc.email()).isEqualTo("user1@mail.com");
+    assertThat(userDoc.active()).isTrue();
+    assertThat(userDoc.scmAccounts()).containsOnly("user_1", "u1");
+    assertThat(userDoc.createdAt()).isEqualTo(1500000000000L);
+    assertThat(userDoc.updatedAt()).isEqualTo(1500000000000L);
+  }
+
+  @Test
+  public void fail_to_get_by_login_on_unknown_user() throws Exception {
+    try {
+      index.getByLogin("unknown");
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("User 'unknown' not found");
+    }
+  }
+
+}
index 194dedc577852d94a458c30f881eb17ea87dd073..56b3d282b95c175d544f81acec6617c988c20bf6 100644 (file)
@@ -58,6 +58,7 @@ public class UserIndexerTest {
     List<UserDoc> docs = esTester.getDocuments("users", "user", UserDoc.class);
     assertThat(docs).hasSize(1);
     UserDoc doc = docs.get(0);
+    assertThat(doc.login()).isEqualTo("user1");
     assertThat(doc.name()).isEqualTo("User1");
     assertThat(doc.email()).isEqualTo("user1@mail.com");
     assertThat(doc.active()).isTrue();
index 56c88b139662889cdfb9a9cbd6c77c0a8e69b63c..55bed235cd66a7532258452b89a546fad30d7b00 100644 (file)
@@ -24,9 +24,11 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.server.user.UserService;
 import org.sonar.server.ws.WsTester;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
 
 public class UsersWsTest {
 
@@ -34,7 +36,7 @@ public class UsersWsTest {
 
   @Before
   public void setUp() throws Exception {
-    WsTester tester = new WsTester(new UsersWs());
+    WsTester tester = new WsTester(new UsersWs(new CreateAction(mock(UserService.class))));
     controller = tester.controller("api/users");
   }
 
@@ -61,7 +63,6 @@ public class UsersWsTest {
     WebService.Action action = controller.action("create");
     assertThat(action).isNotNull();
     assertThat(action.isPost()).isTrue();
-    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
     assertThat(action.params()).hasSize(6);
   }
 
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/index/UserIndexTest/get_nullable_by_login.json b/server/sonar-server/src/test/resources/org/sonar/server/user/index/UserIndexTest/get_nullable_by_login.json
new file mode 100644 (file)
index 0000000..7c29066
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "login": "user1",
+  "name": "User1",
+  "email": "user1@mail.com",
+  "active": true,
+  "scmAccounts": ["user_1", "u1"],
+  "createdAt": 1500000000000,
+  "updatedAt": 1500000000000
+}