]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5934 Rewrite create users WS, SONAR-5961 Rewrite the update user WS
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 18 Dec 2014 14:54:29 +0000 (15:54 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 18 Dec 2014 16:03:50 +0000 (17:03 +0100)
37 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/user/DefaultUserService.java
server/sonar-server/src/main/java/org/sonar/server/user/NewUser.java
server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/user/UserService.java
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWs.java
server/sonar-server/src/main/java/org/sonar/server/ws/ServletRequest.java
server/sonar-server/src/test/java/org/sonar/server/user/DefaultUserServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/user/UserServiceMediumTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java
server/sonar-server/src/test/java/org/sonar/server/ws/WebServiceEngineTest.java
server/sonar-server/src/test/java/org/sonar/server/ws/WsTester.java
server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/associate_default_groups_when_reactivating_user.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/associate_default_groups_when_updating_user.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_reactivate_user_if_not_disabled.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/reactivate_user.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/update_user.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/ws/CreateActionTest/reactivate_user.json [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/ws/CreateActionTest/return_409_when_reactive_exception.json [deleted file]
server/sonar-server/src/test/resources/org/sonar/server/user/ws/UpdateActionTest/update_user.json [new file with mode: 0644]
sonar-core/src/main/resources/org/sonar/l10n/core.properties
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/Request.java
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/internal/SimpleGetRequest.java
sonar-plugin-api/src/test/java/org/sonar/api/server/ws/RequestTest.java
sonar-plugin-api/src/test/java/org/sonar/api/server/ws/internal/SimpleGetRequestTest.java

index 26fb763fce5a123f13130c97f96049bf7b302ba6..374b8174cc6be9df87fe2f9a546fe459e680eba4 100644 (file)
@@ -470,13 +470,14 @@ class ServerComponents {
     pico.addSingleton(DefaultUserService.class);
     pico.addSingleton(UsersWs.class);
     pico.addSingleton(org.sonar.server.user.ws.CreateAction.class);
+    pico.addSingleton(org.sonar.server.user.ws.UpdateAction.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);
+    pico.addSingleton(UserUpdater.class);
 
     // groups
     pico.addSingleton(GroupMembershipService.class);
index d30ee2bbf867070e157049d25cd5815900cbb836..df1cf9691c7ad3eae32d579f6466bd11428427d8 100644 (file)
@@ -82,6 +82,27 @@ public class DefaultUserService implements RubyUserService {
     userService.index();
   }
 
+  public void create(Map<String, Object> params) {
+    NewUser newUser = NewUser.create()
+      .setLogin((String) params.get("login"))
+      .setName((String) params.get("name"))
+      .setEmail((String) params.get("email"))
+      .setScmAccounts((RubyUtils.toStrings(params.get("scm_accounts"))))
+      .setPassword((String) params.get("password"))
+      .setPasswordConfirmation((String) params.get("password_confirmation"));
+    userService.create(newUser);
+  }
+
+  public void update(Map<String, Object> params) {
+    UpdateUser updateUser = UpdateUser.create((String) params.get("login"))
+      .setName((String) params.get("name"))
+      .setEmail((String) params.get("email"))
+      .setScmAccounts((RubyUtils.toStrings(params.get("scm_accounts"))))
+      .setPassword((String) params.get("password"))
+      .setPasswordConfirmation((String) params.get("password_confirmation"));
+    userService.update(updateUser);
+  }
+
   public void index() {
     userService.index();
   }
index 833ecea8461e6003ecd668554c59356db94f5dfc..64a96f77890e6aee49c5af220cc72c0bbd9a5b75 100644 (file)
@@ -35,26 +35,26 @@ public class NewUser {
   private String password;
   private String passwordConfirmation;
 
-  private boolean preventReactivation = false;
-
   private NewUser() {
     // No direct call to this constructor
   }
 
-  public NewUser setLogin(String login) {
+  public NewUser setLogin(@Nullable String login) {
     this.login = login;
     return this;
   }
 
+  @Nullable
   public String login() {
     return login;
   }
 
+  @Nullable
   public String name() {
     return name;
   }
 
-  public NewUser setName(String name) {
+  public NewUser setName(@Nullable String name) {
     this.name = name;
     return this;
   }
@@ -69,15 +69,17 @@ public class NewUser {
     return this;
   }
 
+  @Nullable
   public List<String> scmAccounts() {
     return scmAccounts;
   }
 
-  public NewUser setScmAccounts(List<String> scmAccounts) {
+  public NewUser setScmAccounts(@Nullable List<String> scmAccounts) {
     this.scmAccounts = scmAccounts;
     return this;
   }
 
+  @Nullable
   public String password() {
     return password;
   }
@@ -87,27 +89,16 @@ public class NewUser {
     return this;
   }
 
+  @Nullable
   public String passwordConfirmation() {
     return passwordConfirmation;
   }
 
-  public NewUser setPasswordConfirmation(String passwordConfirmation) {
+  public NewUser setPasswordConfirmation(@Nullable String passwordConfirmation) {
     this.passwordConfirmation = passwordConfirmation;
     return this;
   }
 
-  public boolean isPreventReactivation() {
-    return preventReactivation;
-  }
-
-  /**
-   * When true, if the user already exists in status disabled, an {@link org.sonar.server.user.ReactivationException} will be thrown
-   */
-  public NewUser setPreventReactivation(boolean preventReactivation) {
-    this.preventReactivation = preventReactivation;
-    return this;
-  }
-
   public static NewUser create() {
     return new NewUser();
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java b/server/sonar-server/src/main/java/org/sonar/server/user/ReactivationException.java
deleted file mode 100644 (file)
index 92b9fde..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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;
-
-public class ReactivationException extends RuntimeException {
-
-  private String login;
-
-  public ReactivationException(String message, String login) {
-    super(message);
-    this.login = login;
-  }
-
-  public String login() {
-    return login;
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java b/server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java
new file mode 100644 (file)
index 0000000..9df8c29
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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;
+
+import com.google.common.base.Preconditions;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+public class UpdateUser {
+
+  private String login;
+  private String name;
+  private String email;
+  private List<String> scmAccounts;
+
+  private String password;
+  private String passwordConfirmation;
+
+  boolean isNameChanged, isEmailChanged, isScmAccountsChanged, isPasswordChanged;
+
+  private UpdateUser(String login) {
+    // No direct call to this constructor
+    this.login = login;
+  }
+
+  public String login() {
+    return login;
+  }
+
+  @CheckForNull
+  public String name() {
+    return name;
+  }
+
+  public UpdateUser setName(@Nullable String name) {
+    this.name = name;
+    isNameChanged = true;
+    return this;
+  }
+
+  @CheckForNull
+  public String email() {
+    return email;
+  }
+
+  public UpdateUser setEmail(@Nullable String email) {
+    this.email = email;
+    isEmailChanged = true;
+    return this;
+  }
+
+  @CheckForNull
+  public List<String> scmAccounts() {
+    return scmAccounts;
+  }
+
+  public UpdateUser setScmAccounts(@Nullable List<String> scmAccounts) {
+    this.scmAccounts = scmAccounts;
+    isScmAccountsChanged = true;
+    return this;
+  }
+
+  @CheckForNull
+  public String password() {
+    return password;
+  }
+
+  public UpdateUser setPassword(@Nullable String password) {
+    this.password = password;
+    isPasswordChanged = true;
+    return this;
+  }
+
+  @CheckForNull
+  public String passwordConfirmation() {
+    return passwordConfirmation;
+  }
+
+  public UpdateUser setPasswordConfirmation(@Nullable String passwordConfirmation) {
+    this.passwordConfirmation = passwordConfirmation;
+    isPasswordChanged = true;
+    return this;
+  }
+
+  public boolean isNameChanged() {
+    return isNameChanged;
+  }
+
+  public boolean isEmailChanged() {
+    return isEmailChanged;
+  }
+
+  public boolean isScmAccountsChanged() {
+    return isScmAccountsChanged;
+  }
+
+  public boolean isPasswordChanged() {
+    return isPasswordChanged;
+  }
+
+  public static UpdateUser create(String login) {
+    Preconditions.checkNotNull(login);
+    return new UpdateUser(login);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserCreator.java
deleted file mode 100644 (file)
index 0b4438a..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * 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;
-
-import com.google.common.base.Predicate;
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.platform.NewUserHandler;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.text.CsvWriter;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.user.GroupDto;
-import org.sonar.core.user.UserDto;
-import org.sonar.core.user.UserGroupDto;
-import org.sonar.server.db.DbClient;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.Message;
-import org.sonar.server.user.db.UserGroupDao;
-import org.sonar.server.util.Validation;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.io.StringWriter;
-import java.security.SecureRandom;
-import java.util.List;
-import java.util.Random;
-
-import static com.google.common.collect.Lists.newArrayList;
-
-public class UserCreator {
-
-  private static final String LOGIN_PARAM = "Login";
-  private static final String PASSWORD_CONFIRMATION_PARAM = "Password confirmation";
-  private static final String PASSWORD_PARAM = "Password";
-  private static final String NAME_PARAM = "Name";
-  private static final String EMAIL_PARAM = "Email";
-
-  private final NewUserNotifier newUserNotifier;
-  private final Settings settings;
-  private final UserGroupDao userGroupDao;
-  private final DbClient dbClient;
-  private final System2 system2;
-
-  public UserCreator(NewUserNotifier newUserNotifier, Settings settings, UserGroupDao userGroupDao, DbClient dbClient, System2 system2) {
-    this.newUserNotifier = newUserNotifier;
-    this.settings = settings;
-    this.userGroupDao = userGroupDao;
-    this.dbClient = dbClient;
-    this.system2 = system2;
-  }
-
-  public void create(NewUser newUser) {
-    validate(newUser);
-
-    DbSession dbSession = dbClient.openSession(false);
-    try {
-      String login = newUser.login();
-      UserDto existingUser = dbClient.userDao().selectNullableByLogin(dbSession, login);
-      if (existingUser != null) {
-        updateExistingUser(dbSession, newUser);
-      } else {
-        createNewUser(dbSession, newUser);
-      }
-      dbSession.commit();
-      notifyNewUser(newUser);
-    } finally {
-      dbSession.close();
-    }
-  }
-
-  private static void validate(NewUser newUser) {
-    List<Message> messages = newArrayList();
-
-    validateLogin(newUser.login(), messages);
-    validateName(newUser.name(), messages);
-    validateEmail(newUser.email(), messages);
-    validatePassword(newUser, messages);
-
-    if (!messages.isEmpty()) {
-      throw new BadRequestException(messages);
-    }
-  }
-
-  private static void validateLogin(@Nullable String login, List<Message> messages) {
-    if (Strings.isNullOrEmpty(login)) {
-      messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, LOGIN_PARAM));
-    } else if (!login.matches("\\A\\w[\\w\\.\\-_@\\s]+\\z")) {
-      messages.add(Message.of("user.bad_login"));
-    } else if (login.length() <= 2) {
-      messages.add(Message.of(Validation.IS_TOO_SHORT_MESSAGE, LOGIN_PARAM, 2));
-    } else if (login.length() >= 255) {
-      messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, LOGIN_PARAM, 255));
-    }
-  }
-
-  private static void validateName(@Nullable String name, List<Message> messages) {
-    if (Strings.isNullOrEmpty(name)) {
-      messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, NAME_PARAM));
-    } else if (name.length() >= 200) {
-      messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, NAME_PARAM, 200));
-    }
-  }
-
-  private static void validateEmail(@Nullable String email, List<Message> messages) {
-    if (!Strings.isNullOrEmpty(email) && email.length() >= 100) {
-      messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, EMAIL_PARAM, 100));
-    }
-  }
-
-  private static void validatePassword(NewUser newUser, List<Message> messages) {
-    if (Strings.isNullOrEmpty(newUser.password())) {
-      messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD_PARAM));
-    }
-    if (Strings.isNullOrEmpty(newUser.passwordConfirmation())) {
-      messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, PASSWORD_CONFIRMATION_PARAM));
-    }
-
-    if (!Strings.isNullOrEmpty(newUser.password()) && !Strings.isNullOrEmpty(newUser.passwordConfirmation())
-      && !StringUtils.equals(newUser.password(), newUser.passwordConfirmation())) {
-      messages.add(Message.of("user.password_doesnt_match_confirmation"));
-    }
-  }
-
-  private void createNewUser(DbSession dbSession, NewUser newUser) {
-    long now = system2.now();
-    UserDto userDto = new UserDto()
-      .setLogin(newUser.login())
-      .setName(newUser.name())
-      .setEmail(newUser.email())
-      .setActive(true)
-      .setScmAccounts(convertScmAccountsToCsv(newUser))
-      .setCreatedAt(now)
-      .setUpdatedAt(now);
-    setEncryptedPassWord(newUser, userDto);
-    dbClient.userDao().insert(dbSession, userDto);
-    addDefaultGroup(dbSession, userDto);
-  }
-
-  private void updateExistingUser(DbSession dbSession, NewUser newUser) {
-    String login = newUser.login();
-    UserDto existingUser = dbClient.userDao().selectNullableByLogin(dbSession, login);
-    if (existingUser != null) {
-      if (!existingUser.isActive()) {
-        if (newUser.isPreventReactivation()) {
-          throw new ReactivationException(String.format("A disabled user with the login '%s' already exists", login), login);
-        } else {
-          existingUser.setActive(true);
-          existingUser.setUpdatedAt(system2.now());
-          dbClient.userDao().update(dbSession, existingUser);
-          addDefaultGroup(dbSession, existingUser);
-        }
-      } else {
-        throw new IllegalArgumentException(String.format("A user with the login '%s' already exists", login));
-      }
-    }
-  }
-
-  private static void setEncryptedPassWord(NewUser newUser, UserDto userDto) {
-    Random random = new SecureRandom();
-    byte[] salt = new byte[32];
-    random.nextBytes(salt);
-    String saltHex = DigestUtils.sha1Hex(salt);
-    userDto.setSalt(saltHex);
-    userDto.setCryptedPassword(DigestUtils.sha1Hex("--" + saltHex + "--" + newUser.password() + "--"));
-  }
-
-  @CheckForNull
-  private static String convertScmAccountsToCsv(NewUser newUser) {
-    List<String> scmAccounts = newUser.scmAccounts();
-    if (scmAccounts != null) {
-      int size = newUser.scmAccounts().size();
-      StringWriter writer = new StringWriter(size);
-      CsvWriter csv = CsvWriter.of(writer);
-      csv.values(newUser.scmAccounts().toArray(new String[size]));
-      csv.close();
-      return writer.toString();
-    }
-    return null;
-  }
-
-  private void notifyNewUser(NewUser newUser) {
-    newUserNotifier.onNewUser(NewUserHandler.Context.builder()
-      .setLogin(newUser.login())
-      .setName(newUser.name())
-      .setEmail(newUser.email())
-      .build());
-  }
-
-  private void addDefaultGroup(DbSession dbSession, UserDto userDto) {
-    final String defaultGroup = settings.getString(CoreProperties.CORE_DEFAULT_GROUP);
-    if (defaultGroup == null) {
-      throw new IllegalStateException(String.format("The default group property '%s' is null", CoreProperties.CORE_DEFAULT_GROUP));
-    }
-    List<GroupDto> userGroups = dbClient.groupDao().findByUserLogin(dbSession, userDto.getLogin());
-    if (!Iterables.any(userGroups, new Predicate<GroupDto>() {
-      @Override
-      public boolean apply(@Nullable GroupDto input) {
-        return input != null && input.getKey().equals(defaultGroup);
-      }
-    })) {
-      GroupDto groupDto = dbClient.groupDao().getByKey(dbSession, defaultGroup);
-      userGroupDao.insert(dbSession, new UserGroupDto().setUserId(userDto.getId()).setGroupId(groupDto.getId()));
-    }
-  }
-
-}
index 9f6945b7a10baa8431dbee9a22a10432638e7e1c..1adfc5bb511a490b1c0c128ebbf150c6934a9189 100644 (file)
@@ -32,17 +32,24 @@ public class UserService implements ServerComponent {
 
   private final UserIndexer userIndexer;
   private final UserIndex userIndex;
-  private final UserCreator userCreator;
+  private final UserUpdater userUpdater;
 
-  public UserService(UserIndexer userIndexer, UserIndex userIndex, UserCreator userCreator) {
+  public UserService(UserIndexer userIndexer, UserIndex userIndex, UserUpdater userUpdater) {
     this.userIndexer = userIndexer;
     this.userIndex = userIndex;
-    this.userCreator = userCreator;
+    this.userUpdater = userUpdater;
   }
 
-  public void create(NewUser newUser) {
+  public boolean create(NewUser newUser) {
     checkPermission();
-    userCreator.create(newUser);
+    boolean result = userUpdater.create(newUser);
+    userIndexer.index();
+    return result;
+  }
+
+  public void update(UpdateUser updateUser) {
+    checkPermission();
+    userUpdater.update(updateUser);
     userIndexer.index();
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
new file mode 100644 (file)
index 0000000..d657020
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * 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;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.config.Settings;
+import org.sonar.api.platform.NewUserHandler;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.text.CsvWriter;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.UserDto;
+import org.sonar.core.user.UserGroupDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.Message;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.db.UserGroupDao;
+import org.sonar.server.util.Validation;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.io.StringWriter;
+import java.security.SecureRandom;
+import java.util.List;
+import java.util.Random;
+
+import static com.google.common.collect.Lists.newArrayList;
+
+public class UserUpdater implements ServerComponent {
+
+  private static final String LOGIN_PARAM = "Login";
+  private static final String PASSWORD_CONFIRMATION_PARAM = "Password confirmation";
+  private static final String PASSWORD_PARAM = "Password";
+  private static final String NAME_PARAM = "Name";
+  private static final String EMAIL_PARAM = "Email";
+
+  private final NewUserNotifier newUserNotifier;
+  private final Settings settings;
+  private final UserGroupDao userGroupDao;
+  private final DbClient dbClient;
+  private final System2 system2;
+
+  public UserUpdater(NewUserNotifier newUserNotifier, Settings settings, UserGroupDao userGroupDao, DbClient dbClient, System2 system2) {
+    this.newUserNotifier = newUserNotifier;
+    this.settings = settings;
+    this.userGroupDao = userGroupDao;
+    this.dbClient = dbClient;
+    this.system2 = system2;
+  }
+
+  /**
+   * Retuen true if the user has been reactivated
+   * @return
+   */
+  public boolean create(NewUser newUser) {
+    UserDto userDto = createNewUserDto(newUser);
+    boolean isUserReactivated = false;
+
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      String login = userDto.getLogin();
+      UserDto existingUser = dbClient.userDao().selectNullableByLogin(dbSession, login);
+      if (existingUser == null) {
+        saveUser(dbSession, userDto);
+      } else {
+        if (existingUser.isActive()) {
+          throw new IllegalArgumentException(String.format("An active user with login '%s' already exists", login));
+        }
+        UpdateUser updateUser = UpdateUser.create(login)
+          .setName(newUser.name())
+          .setEmail(newUser.email())
+          .setScmAccounts(newUser.scmAccounts())
+          .setPassword(newUser.password())
+          .setPasswordConfirmation(newUser.passwordConfirmation());
+        updateUserDto(updateUser, existingUser);
+        updateUser(dbSession, existingUser);
+        isUserReactivated = true;
+      }
+      dbSession.commit();
+      notifyNewUser(userDto.getLogin(), userDto.getName(), newUser.email());
+    } finally {
+      dbSession.close();
+    }
+    return isUserReactivated;
+  }
+
+  public void update(UpdateUser updateUser) {
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      UserDto user = dbClient.userDao().selectNullableByLogin(dbSession, updateUser.login());
+      if (user != null) {
+        updateUserDto(updateUser, user);
+        updateUser(dbSession, user);
+      } else {
+        throw new NotFoundException(String.format("User '%s' does not exists", updateUser.login()));
+      }
+      dbSession.commit();
+      notifyNewUser(user.getLogin(), user.getName(), user.getEmail());
+    } finally {
+      dbSession.close();
+    }
+  }
+
+  private static UserDto createNewUserDto(NewUser newUser) {
+    UserDto userDto = new UserDto();
+    List<Message> messages = newArrayList();
+
+    String login = newUser.login();
+    validateLoginFormat(login, messages);
+    userDto.setLogin(login);
+
+    String name = newUser.name();
+    validateNameFormat(name, messages);
+    userDto.setName(name);
+
+    String email = newUser.email();
+    if (email != null) {
+      validateEmailFormat(email, messages);
+      userDto.setEmail(email);
+    }
+
+    String password = newUser.password();
+    String passwordConfirmation = newUser.passwordConfirmation();
+    validatePasswords(password, passwordConfirmation, messages);
+    setEncryptedPassWord(password, userDto);
+
+    userDto.setScmAccounts(convertScmAccountsToCsv(newUser.scmAccounts()));
+
+    if (!messages.isEmpty()) {
+      throw new BadRequestException(messages);
+    }
+    return userDto;
+  }
+
+  private static void updateUserDto(UpdateUser updateUser, UserDto userDto) {
+    List<Message> messages = newArrayList();
+
+    String name = updateUser.name();
+    if (updateUser.isNameChanged()) {
+      validateNameFormat(name, messages);
+      userDto.setName(name);
+    }
+
+    String email = updateUser.email();
+    if (updateUser.isEmailChanged()) {
+      validateEmailFormat(email, messages);
+      userDto.setEmail(email);
+    }
+
+    String password = updateUser.password();
+    String passwordConfirmation = updateUser.passwordConfirmation();
+    if (updateUser.isPasswordChanged()) {
+      validatePasswords(password, passwordConfirmation, messages);
+      setEncryptedPassWord(password, userDto);
+    }
+
+    if (updateUser.isScmAccountsChanged()) {
+      userDto.setScmAccounts(convertScmAccountsToCsv(updateUser.scmAccounts()));
+    }
+
+    if (!messages.isEmpty()) {
+      throw new BadRequestException(messages);
+    }
+  }
+
+  private static void checkNotEmptyParam(@Nullable String value, String param, List<Message> messages) {
+    if (Strings.isNullOrEmpty(value)) {
+      messages.add(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, param));
+    }
+  }
+
+  private static void validateLoginFormat(@Nullable String login, List<Message> messages) {
+    checkNotEmptyParam(login, LOGIN_PARAM, messages);
+    if (!Strings.isNullOrEmpty(login)) {
+      if (!login.matches("\\A\\w[\\w\\.\\-_@\\s]+\\z")) {
+        messages.add(Message.of("user.bad_login"));
+      } else if (login.length() <= 2) {
+        messages.add(Message.of(Validation.IS_TOO_SHORT_MESSAGE, LOGIN_PARAM, 2));
+      } else if (login.length() >= 255) {
+        messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, LOGIN_PARAM, 255));
+      }
+    }
+  }
+
+  private static void validateNameFormat(@Nullable String name, List<Message> messages) {
+    checkNotEmptyParam(name, NAME_PARAM, messages);
+    if (name != null && name.length() >= 200) {
+      messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, NAME_PARAM, 200));
+    }
+  }
+
+  private static void validateEmailFormat(@Nullable String email, List<Message> messages) {
+    if (email != null) {
+      if (email.length() >= 100) {
+        messages.add(Message.of(Validation.IS_TOO_LONG_MESSAGE, EMAIL_PARAM, 100));
+      }
+    }
+  }
+
+  private static void validatePasswords(@Nullable String password, @Nullable String passwordConfirmation, List<Message> messages) {
+    checkNotEmptyParam(password, PASSWORD_PARAM, messages);
+    checkNotEmptyParam(passwordConfirmation, PASSWORD_CONFIRMATION_PARAM, messages);
+    if (!StringUtils.equals(password, passwordConfirmation)) {
+      messages.add(Message.of("user.password_doesnt_match_confirmation"));
+    }
+  }
+
+  private void saveUser(DbSession dbSession, UserDto userDto) {
+    long now = system2.now();
+    userDto.setActive(true).setCreatedAt(now).setUpdatedAt(now);
+    dbClient.userDao().insert(dbSession, userDto);
+    addDefaultGroup(dbSession, userDto);
+  }
+
+  private void updateUser(DbSession dbSession, UserDto userDto) {
+    long now = system2.now();
+    userDto.setActive(true).setUpdatedAt(now);
+    dbClient.userDao().update(dbSession, userDto);
+    addDefaultGroup(dbSession, userDto);
+  }
+
+  private static void setEncryptedPassWord(String password, UserDto userDto) {
+    Random random = new SecureRandom();
+    byte[] salt = new byte[32];
+    random.nextBytes(salt);
+    String saltHex = DigestUtils.sha1Hex(salt);
+    userDto.setSalt(saltHex);
+    userDto.setCryptedPassword(DigestUtils.sha1Hex("--" + saltHex + "--" + password + "--"));
+  }
+
+  @CheckForNull
+  private static String convertScmAccountsToCsv(@Nullable List<String> scmAccounts) {
+    if (scmAccounts != null) {
+      int size = scmAccounts.size();
+      StringWriter writer = new StringWriter(size);
+      CsvWriter csv = CsvWriter.of(writer);
+      csv.values(scmAccounts.toArray(new String[size]));
+      csv.close();
+      return writer.toString();
+    }
+    return null;
+  }
+
+  private void notifyNewUser(String login, String name, String email) {
+    newUserNotifier.onNewUser(NewUserHandler.Context.builder()
+      .setLogin(login)
+      .setName(name)
+      .setEmail(email)
+      .build());
+  }
+
+  private void addDefaultGroup(DbSession dbSession, UserDto userDto) {
+    final String defaultGroup = settings.getString(CoreProperties.CORE_DEFAULT_GROUP);
+    if (defaultGroup == null) {
+      throw new IllegalStateException(String.format("The default group property '%s' is null", CoreProperties.CORE_DEFAULT_GROUP));
+    }
+    List<GroupDto> userGroups = dbClient.groupDao().findByUserLogin(dbSession, userDto.getLogin());
+    if (!Iterables.any(userGroups, new Predicate<GroupDto>() {
+      @Override
+      public boolean apply(@Nullable GroupDto input) {
+        return input != null && input.getKey().equals(defaultGroup);
+      }
+    })) {
+      GroupDto groupDto = dbClient.groupDao().getByKey(dbSession, defaultGroup);
+      userGroupDao.insert(dbSession, new UserGroupDto().setUserId(userDto.getId()).setGroupId(groupDto.getId()));
+    }
+  }
+}
index cb5ea34f576d2d8ecd9f9a4dc8c73549e395fc46..3977830d023c384c2b4ce2482749194ec0e4318a 100644 (file)
 
 package org.sonar.server.user.ws;
 
+import org.sonar.api.i18n.I18n;
 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.api.utils.text.JsonWriter;
-import org.sonar.server.plugins.MimeTypes;
 import org.sonar.server.user.NewUser;
-import org.sonar.server.user.ReactivationException;
 import org.sonar.server.user.UserService;
+import org.sonar.server.user.UserSession;
 import org.sonar.server.user.index.UserDoc;
 
-import java.io.OutputStreamWriter;
-
 public class CreateAction implements RequestHandler {
 
   private static final String PARAM_LOGIN = "login";
@@ -41,12 +39,13 @@ public class CreateAction implements RequestHandler {
   private static final String PARAM_NAME = "name";
   private static final String PARAM_EMAIL = "email";
   private static final String PARAM_SCM_ACCOUNTS = "scm_accounts";
-  private static final String PARAM_PREVENT_REACTIVATION = "prevent_reactivation";
 
   private final UserService service;
+  private final I18n i18n;
 
-  public CreateAction(UserService service) {
+  public CreateAction(UserService service, I18n i18n) {
     this.service = service;
+    this.i18n = i18n;
   }
 
   void define(WebService.NewController controller) {
@@ -81,13 +80,8 @@ public class CreateAction implements RequestHandler {
       .setExampleValue("myname@email.com");
 
     action.createParam(PARAM_SCM_ACCOUNTS)
-      .setDescription("SCM accounts")
+      .setDescription("SCM accounts. This parameter has been added in 5.1")
       .setExampleValue("myscmaccount1, myscmaccount2");
-
-    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
@@ -99,37 +93,24 @@ public class CreateAction implements RequestHandler {
       .setEmail(request.param(PARAM_EMAIL))
       .setScmAccounts(request.paramAsStrings(PARAM_SCM_ACCOUNTS))
       .setPassword(request.mandatoryParam(PARAM_PASSWORD))
-      .setPasswordConfirmation(request.mandatoryParam(PARAM_PASSWORD_CONFIRMATION))
-      .setPreventReactivation(request.mandatoryParamAsBoolean(PARAM_PREVENT_REACTIVATION));
-
-    try {
-      service.create(newUser);
-      writeResponse(response, login);
-    } catch (ReactivationException e) {
-      write409(response, login);
-    }
-  }
+      .setPasswordConfirmation(request.mandatoryParam(PARAM_PASSWORD_CONFIRMATION));
 
-  private void writeResponse(Response response, String login) {
-    UserDoc user = service.getByLogin(login);
-    JsonWriter json = response.newJsonWriter().beginObject().name("user");
-    writeUser(json, user);
-    json.endObject().close();
+    boolean isUserReactivated = service.create(newUser);
+    writeResponse(response, login, isUserReactivated);
   }
 
-  private void write409(Response response, String login) {
+  private void writeResponse(Response response, String login, boolean isUserReactivated) {
     UserDoc user = service.getByLogin(login);
-
-    Response.Stream stream = response.stream();
-    stream.setStatus(409);
-    stream.setMediaType(MimeTypes.JSON);
-    JsonWriter json = JsonWriter.of(new OutputStreamWriter(stream.output())).beginObject().name("user");
+    JsonWriter json = response.newJsonWriter().beginObject();
     writeUser(json, user);
+    if (isUserReactivated) {
+      writeReactivationMessage(json, login);
+    }
     json.endObject().close();
   }
 
   private void writeUser(JsonWriter json, UserDoc user) {
-    json.beginObject()
+    json.name("user").beginObject()
       .prop("login", user.login())
       .prop("name", user.name())
       .prop("email", user.email())
@@ -137,4 +118,13 @@ public class CreateAction implements RequestHandler {
       .name("scmAccounts").beginArray().values(user.scmAccounts()).endArray()
       .endObject();
   }
+
+  private void writeReactivationMessage(JsonWriter json, String login) {
+    json.name("infos").beginArray();
+    json.beginObject();
+    String text = i18n.message(UserSession.get().locale(), "user.reactivated", "user.reactivated", login);
+    json.prop("msg", text);
+    json.endObject();
+    json.endArray();
+  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java
new file mode 100644 (file)
index 0000000..4cb0526
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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.api.utils.text.JsonWriter;
+import org.sonar.server.user.UpdateUser;
+import org.sonar.server.user.UserService;
+import org.sonar.server.user.index.UserDoc;
+
+public class UpdateAction 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_SCM_ACCOUNTS = "scm_accounts";
+
+  private final UserService service;
+
+  public UpdateAction(UserService service) {
+    this.service = service;
+  }
+
+  void define(WebService.NewController controller) {
+    WebService.NewAction action = controller.createAction("update")
+      .setDescription("Update 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_SCM_ACCOUNTS)
+      .setDescription("SCM accounts. This parameter has been added in 5.1")
+      .setExampleValue("myscmaccount1, myscmaccount2");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String login = request.mandatoryParam(PARAM_LOGIN);
+    UpdateUser updateUser = UpdateUser.create(login);
+    if (request.hasParam(PARAM_NAME)) {
+      updateUser.setName(request.mandatoryParam(PARAM_NAME));
+    }
+    if (request.hasParam(PARAM_EMAIL)) {
+      updateUser.setEmail(request.param(PARAM_EMAIL));
+    }
+    if (request.hasParam(PARAM_SCM_ACCOUNTS)) {
+      updateUser.setScmAccounts(request.paramAsStrings(PARAM_SCM_ACCOUNTS));
+    }
+    if (request.hasParam(PARAM_PASSWORD)) {
+      updateUser.setPassword(request.mandatoryParam(PARAM_PASSWORD));
+    }
+    if (request.hasParam(PARAM_PASSWORD_CONFIRMATION)) {
+      updateUser.setPasswordConfirmation(request.mandatoryParam(PARAM_PASSWORD_CONFIRMATION));
+    }
+
+    service.update(updateUser);
+    writeResponse(response, login);
+  }
+
+  private void writeResponse(Response response, String login) {
+    UserDoc user = service.getByLogin(login);
+    JsonWriter json = response.newJsonWriter().beginObject();
+    writeUser(json, user);
+    json.endObject().close();
+  }
+
+  private void writeUser(JsonWriter json, UserDoc user) {
+    json.name("user").beginObject()
+      .prop("login", user.login())
+      .prop("name", user.name())
+      .prop("email", user.email())
+      .prop("active", user.active())
+      .name("scmAccounts").beginArray().values(user.scmAccounts()).endArray()
+      .endObject();
+  }
+}
index a5b7d1abdf2d364dcdd0ed9412d68624adfbbe98..4a44637627c4efd013d6abc83cce8fdcf6544d6a 100644 (file)
@@ -27,9 +27,11 @@ import org.sonar.api.server.ws.WebService;
 public class UsersWs implements WebService {
 
   private final CreateAction createAction;
+  private final UpdateAction updateAction;
 
-  public UsersWs(CreateAction createAction) {
+  public UsersWs(CreateAction createAction, UpdateAction updateAction) {
     this.createAction = createAction;
+    this.updateAction = updateAction;
   }
 
   @Override
@@ -40,7 +42,7 @@ public class UsersWs implements WebService {
 
     defineSearchAction(controller);
     createAction.define(controller);
-    defineUpdateAction(controller);
+    updateAction.define(controller);
     defineDeactivateAction(controller);
 
     controller.done();
@@ -65,29 +67,6 @@ public class UsersWs implements WebService {
     RailsHandler.addFormatParam(action);
   }
 
-  private void defineUpdateAction(NewController controller) {
-    NewAction action = controller.createAction("update")
-      .setDescription("Update 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("name")
-      .setDescription("User name")
-      .setExampleValue("My New Name");
-
-    action.createParam("email")
-      .setDescription("User email")
-      .setExampleValue("mynewname@email.com");
-
-    RailsHandler.addFormatParam(action);
-  }
-
   private void defineDeactivateAction(NewController controller) {
     NewAction action = controller.createAction("deactivate")
       .setDescription("Deactivate a user. Requires Administer System permission")
index ca1e4e66224303d88a9fb3e91469ff77788d730f..6a863ee5d405a9fdbe8291bde1debd2be48d29d0 100644 (file)
@@ -42,6 +42,11 @@ public class ServletRequest extends ValidatingRequest {
     return source.getMethod();
   }
 
+  @Override
+  public boolean hasParam(String key) {
+    return source.getParameterMap().containsKey(key) || params.keySet().contains(key);
+  }
+
   @Override
   protected String readParam(String key) {
     String value = source.getParameter(key);
index 17b3107387f56157d3dfd95af95a61f14495a1bf..4d87662e4e9ccf6bbf51edb2583a012538ac893d 100644 (file)
@@ -24,7 +24,10 @@ import com.google.common.collect.Maps;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.user.UserFinder;
 import org.sonar.api.user.UserQuery;
 import org.sonar.core.permission.GlobalPermissions;
@@ -32,11 +35,16 @@ import org.sonar.core.user.UserDao;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.*;
 
+@RunWith(MockitoJUnitRunner.class)
 public class DefaultUserServiceTest {
 
   UserService userService = mock(UserService.class);
@@ -122,6 +130,40 @@ public class DefaultUserServiceTest {
     }
   }
 
+  @Test
+  public void create() throws Exception {
+    Map<String, Object> params = newHashMap();
+    params.put("login", "john");
+    params.put("name", "John");
+    params.put("email", "john@email.com");
+    params.put("scm_accounts", newArrayList("jn"));
+    service.create(params);
+
+    ArgumentCaptor<NewUser> newUserCaptor = ArgumentCaptor.forClass(NewUser.class);
+    verify(userService).create(newUserCaptor.capture());
+    assertThat(newUserCaptor.getValue().login()).isEqualTo("john");
+    assertThat(newUserCaptor.getValue().name()).isEqualTo("John");
+    assertThat(newUserCaptor.getValue().email()).isEqualTo("john@email.com");
+    assertThat(newUserCaptor.getValue().scmAccounts()).containsOnly("jn");
+  }
+
+  @Test
+  public void update() throws Exception {
+    Map<String, Object> params = newHashMap();
+    params.put("login", "john");
+    params.put("name", "John");
+    params.put("email", "john@email.com");
+    params.put("scm_accounts", newArrayList("jn"));
+    service.update(params);
+
+    ArgumentCaptor<UpdateUser> userCaptor = ArgumentCaptor.forClass(UpdateUser.class);
+    verify(userService).update(userCaptor.capture());
+    assertThat(userCaptor.getValue().login()).isEqualTo("john");
+    assertThat(userCaptor.getValue().name()).isEqualTo("John");
+    assertThat(userCaptor.getValue().email()).isEqualTo("john@email.com");
+    assertThat(userCaptor.getValue().scmAccounts()).containsOnly("jn");
+  }
+
   @Test
   public void index() throws Exception {
     service.index();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserCreatorTest.java
deleted file mode 100644 (file)
index e76ca21..0000000
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * 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;
-
-import com.google.common.base.Strings;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.platform.NewUserHandler;
-import org.sonar.api.utils.System2;
-import org.sonar.core.persistence.DbSession;
-import org.sonar.core.persistence.DbTester;
-import org.sonar.core.user.GroupDto;
-import org.sonar.core.user.GroupMembershipDao;
-import org.sonar.core.user.GroupMembershipQuery;
-import org.sonar.core.user.UserDto;
-import org.sonar.server.db.DbClient;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.Message;
-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.util.Validation;
-import org.sonar.test.DbTests;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@Category(DbTests.class)
-@RunWith(MockitoJUnitRunner.class)
-public class UserCreatorTest {
-
-  @Rule
-  public DbTester db = new DbTester();
-
-  @Mock
-  System2 system2;
-
-  @Mock
-  NewUserNotifier newUserNotifier;
-
-  @Captor
-  ArgumentCaptor<NewUserHandler.Context> newUserHandler;
-
-  Settings settings;
-  UserDao userDao;
-  GroupDao groupDao;
-  GroupMembershipFinder groupMembershipFinder;
-  DbSession session;
-
-  UserCreator userCreator;
-
-  @Before
-  public void setUp() throws Exception {
-    settings = new Settings();
-    session = db.myBatis().openSession(false);
-    userDao = new UserDao(db.myBatis(), system2);
-    groupDao = new GroupDao(system2);
-    UserGroupDao userGroupDao = new UserGroupDao();
-    GroupMembershipDao groupMembershipDao = new GroupMembershipDao(db.myBatis());
-    groupMembershipFinder = new GroupMembershipFinder(userDao, groupMembershipDao);
-
-    userCreator = new UserCreator(newUserNotifier, settings, userGroupDao, new DbClient(db.database(), db.myBatis(), userDao, groupDao), system2);
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    session.close();
-  }
-
-  @Test
-  public void create_user() throws Exception {
-    when(system2.now()).thenReturn(1418215735482L);
-    createDefaultGroup();
-
-    userCreator.create(NewUser.create()
-      .setLogin("user")
-      .setName("User")
-      .setEmail("user@mail.com")
-      .setPassword("password")
-      .setPasswordConfirmation("password")
-      .setScmAccounts(newArrayList("u1", "u_1")));
-
-    UserDto dto = userDao.selectNullableByLogin(session, "user");
-    assertThat(dto.getId()).isNotNull();
-    assertThat(dto.getLogin()).isEqualTo("user");
-    assertThat(dto.getName()).isEqualTo("User");
-    assertThat(dto.getEmail()).isEqualTo("user@mail.com");
-    assertThat(dto.getScmAccounts()).contains("u1,u_1");
-    assertThat(dto.isActive()).isTrue();
-
-    assertThat(dto.getSalt()).isNotNull();
-    assertThat(dto.getCryptedPassword()).isNotNull();
-    assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L);
-    assertThat(dto.getUpdatedAt()).isEqualTo(1418215735482L);
-  }
-
-  @Test
-  public void create_user_with_minimum_fields() throws Exception {
-    when(system2.now()).thenReturn(1418215735482L);
-    createDefaultGroup();
-
-    userCreator.create(NewUser.create()
-      .setLogin("user")
-      .setName("User")
-      .setPassword("password")
-      .setPasswordConfirmation("password"));
-
-    assertThat(userDao.selectNullableByLogin(session, "user")).isNotNull();
-  }
-
-  @Test
-  public void fail_to_create_user_if_login_already_exists() throws Exception {
-    db.prepareDbUnit(getClass(), "fail_to_create_user_if_already_exists.xml");
-
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("A user with the login 'marius' already exists");
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_if_login_already_exists_but_inactive() throws Exception {
-    db.prepareDbUnit(getClass(), "fail_to_create_user_if_already_exists_but_inactive.xml");
-
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password")
-        .setPreventReactivation(true));
-      fail();
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(ReactivationException.class).hasMessage("A disabled user with the login 'marius' already exists");
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_missing_login() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin(null)
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Login"));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_invalid_login() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("/marius/")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of("user.bad_login"));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_too_short_login() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("ma")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_SHORT_MESSAGE, "Login", 2));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_too_long_login() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin(Strings.repeat("m", 256))
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Login", 255));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_missing_name() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName(null)
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Name"));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_too_long_name() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName(Strings.repeat("m", 201))
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Name", 200));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_too_long_email() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName("Marius")
-        .setEmail(Strings.repeat("m", 101))
-        .setPassword("password")
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Email", 100));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_missing_password() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword(null)
-        .setPasswordConfirmation("password"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Password"));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_missing_password_confirmation() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation(null));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Password confirmation"));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_password_not_matching_password_confirmation() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("marius")
-        .setName("Marius")
-        .setEmail("marius@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password2"));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).containsOnly(Message.of("user.password_doesnt_match_confirmation"));
-    }
-  }
-
-  @Test
-  public void fail_to_create_user_with_many_errors() throws Exception {
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("")
-        .setName("")
-        .setEmail("marius@mail.com")
-        .setPassword("")
-        .setPasswordConfirmation(""));
-      fail();
-    } catch (BadRequestException e) {
-      assertThat(e.errors().messages()).hasSize(4);
-    }
-  }
-
-  @Test
-  public void notify_new_user() throws Exception {
-    createDefaultGroup();
-
-    userCreator.create(NewUser.create()
-      .setLogin("user")
-      .setName("User")
-      .setEmail("user@mail.com")
-      .setPassword("password")
-      .setPasswordConfirmation("password")
-      .setScmAccounts(newArrayList("u1", "u_1")));
-
-    verify(newUserNotifier).onNewUser(newUserHandler.capture());
-    assertThat(newUserHandler.getValue().getLogin()).isEqualTo("user");
-    assertThat(newUserHandler.getValue().getName()).isEqualTo("User");
-    assertThat(newUserHandler.getValue().getEmail()).isEqualTo("user@mail.com");
-  }
-
-  @Test
-  public void associate_default_groups_when_creating_user() throws Exception {
-    createDefaultGroup();
-
-    userCreator.create(NewUser.create()
-      .setLogin("user")
-      .setName("User")
-      .setEmail("user@mail.com")
-      .setPassword("password")
-      .setPasswordConfirmation("password")
-      .setScmAccounts(newArrayList("u1", "u_1")));
-
-    GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("user").build());
-    assertThat(membership.groups()).hasSize(1);
-    assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users");
-    assertThat(membership.groups().get(0).isMember()).isTrue();
-  }
-
-  @Test
-  public void fail_to_associate_default_groups_to_user_if_no_default_group() throws Exception {
-    settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, (String) null);
-
-    try {
-      userCreator.create(NewUser.create()
-        .setLogin("user")
-        .setName("User")
-        .setEmail("user@mail.com")
-        .setPassword("password")
-        .setPasswordConfirmation("password")
-        .setScmAccounts(newArrayList("u1", "u_1")));
-    } catch (Exception e) {
-      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("The default group property 'sonar.defaultGroup' is null");
-    }
-  }
-
-  @Test
-  public void reactivate_user() throws Exception {
-    db.prepareDbUnit(getClass(), "reactivate_user.xml");
-    when(system2.now()).thenReturn(1418215735486L);
-    createDefaultGroup();
-
-    userCreator.create(NewUser.create()
-      .setLogin("marius")
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .setPasswordConfirmation("password2")
-      .setPreventReactivation(false));
-
-    UserDto dto = userDao.selectNullableByLogin(session, "marius");
-    assertThat(dto.isActive()).isTrue();
-    assertThat(dto.getName()).isEqualTo("Marius");
-    assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr");
-    assertThat(dto.getScmAccounts()).contains("ma,marius33");
-
-    assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
-    assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
-    assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L);
-    assertThat(dto.getUpdatedAt()).isEqualTo(1418215735486L);
-  }
-
-  @Test
-  public void associate_default_groups_when_reactivating_user() throws Exception {
-    db.prepareDbUnit(getClass(), "associate_default_groups_when_reactivating_user.xml");
-    createDefaultGroup();
-
-    userCreator.create(NewUser.create()
-      .setLogin("marius")
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .setPasswordConfirmation("password2")
-      .setPreventReactivation(false));
-
-    GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("marius").groupSearch("sonar-users").build());
-    assertThat(membership.groups()).hasSize(1);
-    assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users");
-    assertThat(membership.groups().get(0).isMember()).isTrue();
-  }
-
-  private void createDefaultGroup() {
-    settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, "sonar-users");
-    groupDao.insert(session, new GroupDto().setName("sonar-users").setDescription("Sonar Users"));
-    session.commit();
-  }
-}
index 164a45f8439fcf850e05951497cab9a5c39dc19b..5072bdee0f0e61715e41e903a51b2ca7f8724465 100644 (file)
@@ -33,7 +33,6 @@ import org.sonar.server.db.DbClient;
 import org.sonar.server.es.EsClient;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.tester.ServerTester;
-import org.sonar.server.user.db.UserDao;
 import org.sonar.server.user.index.UserIndexDefinition;
 
 import static com.google.common.collect.Lists.newArrayList;
@@ -71,7 +70,7 @@ public class UserServiceMediumTest {
     dbClient.groupDao().insert(session, userGroup);
     session.commit();
 
-    service.create(NewUser.create()
+    boolean result = service.create(NewUser.create()
       .setLogin("user")
       .setName("User")
       .setEmail("user@mail.com")
@@ -79,7 +78,8 @@ public class UserServiceMediumTest {
       .setPasswordConfirmation("password")
       .setScmAccounts(newArrayList("u1", "u_1")));
 
-    assertThat(tester.get(UserDao.class).selectNullableByLogin(session, "user")).isNotNull();
+    assertThat(result).isFalse();
+    assertThat(dbClient.userDao().selectNullableByLogin(session, "user")).isNotNull();
     assertThat(esClient.prepareGet(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, "user").get().isExists()).isTrue();
   }
 
@@ -96,6 +96,43 @@ public class UserServiceMediumTest {
       .setScmAccounts(newArrayList("u1", "u_1")));
   }
 
+  @Test
+  public void update_user() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+
+    dbClient.userDao().insert(session, new UserDto()
+      .setLogin("marius")
+      .setName("Marius")
+      .setEmail("marius@mail.com")
+      .setCryptedPassword("1234")
+      .setSalt("abcd")
+      .setCreatedAt(1000L)
+      );
+    GroupDto userGroup = new GroupDto().setName(CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE);
+    dbClient.groupDao().insert(session, userGroup);
+    session.commit();
+
+    service.update(UpdateUser.create("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com"));
+
+    UserDto userDto = dbClient.userDao().selectNullableByLogin(session, "marius");
+    assertThat(userDto.getName()).isEqualTo("Marius2");
+    assertThat(userDto.getEmail()).isEqualTo("marius2@mail.com");
+  }
+
+  @Test(expected = ForbiddenException.class)
+  public void fail_to_update_user_without_sys_admin_permission() throws Exception {
+    MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.DASHBOARD_SHARING);
+
+    service.update(UpdateUser.create("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2")
+      .setScmAccounts(newArrayList("ma2")));
+  }
+
   @Test
   public void get_nullable_by_login() throws Exception {
     MockUserSession.set().setLogin("john").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java
new file mode 100644 (file)
index 0000000..582eaa1
--- /dev/null
@@ -0,0 +1,574 @@
+/*
+ * 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;
+
+import com.google.common.base.Strings;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.platform.NewUserHandler;
+import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.user.GroupDto;
+import org.sonar.core.user.GroupMembershipDao;
+import org.sonar.core.user.GroupMembershipQuery;
+import org.sonar.core.user.UserDto;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.Message;
+import org.sonar.server.exceptions.NotFoundException;
+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.util.Validation;
+import org.sonar.test.DbTests;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@Category(DbTests.class)
+@RunWith(MockitoJUnitRunner.class)
+public class UserUpdaterTest {
+
+  @Rule
+  public DbTester db = new DbTester();
+
+  @Mock
+  System2 system2;
+
+  @Mock
+  NewUserNotifier newUserNotifier;
+
+  @Captor
+  ArgumentCaptor<NewUserHandler.Context> newUserHandler;
+
+  Settings settings;
+  UserDao userDao;
+  GroupDao groupDao;
+  GroupMembershipFinder groupMembershipFinder;
+  DbSession session;
+
+  UserUpdater userUpdater;
+
+  @Before
+  public void setUp() throws Exception {
+    settings = new Settings();
+    session = db.myBatis().openSession(false);
+    userDao = new UserDao(db.myBatis(), system2);
+    groupDao = new GroupDao(system2);
+    UserGroupDao userGroupDao = new UserGroupDao();
+    GroupMembershipDao groupMembershipDao = new GroupMembershipDao(db.myBatis());
+    groupMembershipFinder = new GroupMembershipFinder(userDao, groupMembershipDao);
+
+    userUpdater = new UserUpdater(newUserNotifier, settings, userGroupDao, new DbClient(db.database(), db.myBatis(), userDao, groupDao), system2);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    session.close();
+  }
+
+  @Test
+  public void create_user() throws Exception {
+    when(system2.now()).thenReturn(1418215735482L);
+    createDefaultGroup();
+
+    boolean result = userUpdater.create(NewUser.create()
+      .setLogin("user")
+      .setName("User")
+      .setEmail("user@mail.com")
+      .setPassword("password")
+      .setPasswordConfirmation("password")
+      .setScmAccounts(newArrayList("u1", "u_1")));
+
+    UserDto dto = userDao.selectNullableByLogin(session, "user");
+    assertThat(dto.getId()).isNotNull();
+    assertThat(dto.getLogin()).isEqualTo("user");
+    assertThat(dto.getName()).isEqualTo("User");
+    assertThat(dto.getEmail()).isEqualTo("user@mail.com");
+    assertThat(dto.getScmAccounts()).contains("u1,u_1");
+    assertThat(dto.isActive()).isTrue();
+
+    assertThat(dto.getSalt()).isNotNull();
+    assertThat(dto.getCryptedPassword()).isNotNull();
+    assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L);
+    assertThat(dto.getUpdatedAt()).isEqualTo(1418215735482L);
+    assertThat(result).isFalse();
+  }
+
+  @Test
+  public void create_user_with_minimum_fields() throws Exception {
+    when(system2.now()).thenReturn(1418215735482L);
+    createDefaultGroup();
+
+    userUpdater.create(NewUser.create()
+      .setLogin("user")
+      .setName("User")
+      .setPassword("password")
+      .setPasswordConfirmation("password"));
+
+    assertThat(userDao.selectNullableByLogin(session, "user")).isNotNull();
+  }
+
+  @Test
+  public void fail_to_create_user_with_missing_login() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin(null)
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Login"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_invalid_login() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("/marius/")
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.bad_login"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_too_short_login() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("ma")
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_SHORT_MESSAGE, "Login", 2));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_too_long_login() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin(Strings.repeat("m", 256))
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Login", 255));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_missing_name() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName(null)
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Name"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_too_long_name() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName(Strings.repeat("m", 201))
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Name", 200));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_too_long_email() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius")
+        .setEmail(Strings.repeat("m", 101))
+        .setPassword("password")
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of(Validation.IS_TOO_LONG_MESSAGE, "Email", 100));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_missing_password() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword(null)
+        .setPasswordConfirmation("password"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).contains(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Password"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_missing_password_confirmation() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation(null));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).contains(Message.of(Validation.CANT_BE_EMPTY_MESSAGE, "Password confirmation"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_password_not_matching_password_confirmation() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius")
+        .setEmail("marius@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password2"));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.password_doesnt_match_confirmation"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_with_many_errors() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("")
+        .setName("")
+        .setEmail("marius@mail.com")
+        .setPassword("")
+        .setPasswordConfirmation(""));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).hasSize(4);
+    }
+  }
+
+  @Test
+  public void notify_new_user() throws Exception {
+    createDefaultGroup();
+
+    userUpdater.create(NewUser.create()
+      .setLogin("user")
+      .setName("User")
+      .setEmail("user@mail.com")
+      .setPassword("password")
+      .setPasswordConfirmation("password")
+      .setScmAccounts(newArrayList("u1", "u_1")));
+
+    verify(newUserNotifier).onNewUser(newUserHandler.capture());
+    assertThat(newUserHandler.getValue().getLogin()).isEqualTo("user");
+    assertThat(newUserHandler.getValue().getName()).isEqualTo("User");
+    assertThat(newUserHandler.getValue().getEmail()).isEqualTo("user@mail.com");
+  }
+
+  @Test
+  public void associate_default_groups_when_creating_user() throws Exception {
+    createDefaultGroup();
+
+    userUpdater.create(NewUser.create()
+      .setLogin("user")
+      .setName("User")
+      .setEmail("user@mail.com")
+      .setPassword("password")
+      .setPasswordConfirmation("password")
+      .setScmAccounts(newArrayList("u1", "u_1")));
+
+    GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("user").build());
+    assertThat(membership.groups()).hasSize(1);
+    assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users");
+    assertThat(membership.groups().get(0).isMember()).isTrue();
+  }
+
+  @Test
+  public void fail_to_associate_default_groups_to_user_if_no_default_group() throws Exception {
+    settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, (String) null);
+
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("user")
+        .setName("User")
+        .setEmail("user@mail.com")
+        .setPassword("password")
+        .setPasswordConfirmation("password")
+        .setScmAccounts(newArrayList("u1", "u_1")));
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("The default group property 'sonar.defaultGroup' is null");
+    }
+  }
+
+  @Test
+  public void reactivate_user_when_creating_user_with_existing_login() throws Exception {
+    db.prepareDbUnit(getClass(), "reactivate_user.xml");
+    when(system2.now()).thenReturn(1418215735486L);
+    createDefaultGroup();
+
+    boolean result = userUpdater.create(NewUser.create()
+      .setLogin("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2"));
+    session.commit();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.isActive()).isTrue();
+    assertThat(dto.getName()).isEqualTo("Marius2");
+    assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
+    assertThat(dto.getScmAccounts()).isNull();
+
+    assertThat(dto.getSalt()).isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+    assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L);
+    assertThat(dto.getUpdatedAt()).isEqualTo(1418215735486L);
+
+    assertThat(result).isTrue();
+  }
+
+  @Test
+  public void fail_to_reactivate_user_if_not_disabled() throws Exception {
+    db.prepareDbUnit(getClass(), "fail_to_reactivate_user_if_not_disabled.xml");
+    createDefaultGroup();
+
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius2")
+        .setEmail("marius2@mail.com")
+        .setPassword("password2")
+        .setPasswordConfirmation("password2"));
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("An active user with login 'marius' already exists");
+    }
+  }
+
+  @Test
+  public void associate_default_groups_when_reactivating_user() throws Exception {
+    db.prepareDbUnit(getClass(), "associate_default_groups_when_reactivating_user.xml");
+    createDefaultGroup();
+
+    userUpdater.create(NewUser.create()
+      .setLogin("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2"));
+    session.commit();
+
+    GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("marius").groupSearch("sonar-users").build());
+    assertThat(membership.groups()).hasSize(1);
+    assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users");
+    assertThat(membership.groups().get(0).isMember()).isTrue();
+  }
+
+  @Test
+  public void update_user() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    when(system2.now()).thenReturn(1418215735486L);
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2")
+      .setScmAccounts(newArrayList("ma2")));
+    session.commit();
+    session.clearCache();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.isActive()).isTrue();
+    assertThat(dto.getName()).isEqualTo("Marius2");
+    assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
+    assertThat(dto.getScmAccounts()).contains("ma2");
+
+    assertThat(dto.getSalt()).isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+    assertThat(dto.getCreatedAt()).isEqualTo(1418215735482L);
+    assertThat(dto.getUpdatedAt()).isEqualTo(1418215735486L);
+  }
+
+  @Test
+  public void update_only_user_name() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setName("Marius2"));
+    session.commit();
+    session.clearCache();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.getName()).isEqualTo("Marius2");
+
+    // Following fields has not changed
+    assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr");
+    assertThat(dto.getScmAccounts()).contains("ma,marius33");
+    assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+  }
+
+  @Test
+  public void update_only_user_email() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setEmail("marius2@mail.com"));
+    session.commit();
+    session.clearCache();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
+
+    // Following fields has not changed
+    assertThat(dto.getName()).isEqualTo("Marius");
+    assertThat(dto.getScmAccounts()).contains("ma,marius33");
+    assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+  }
+
+  @Test
+  public void update_only_scm_accounts_email() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setScmAccounts(newArrayList("ma2")));
+    session.commit();
+    session.clearCache();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.getScmAccounts()).contains("ma2");
+
+    // Following fields has not changed
+    assertThat(dto.getName()).isEqualTo("Marius");
+    assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr");
+    assertThat(dto.getSalt()).isEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+  }
+
+  @Test
+  public void update_only_user_password() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2"));
+    session.commit();
+    session.clearCache();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.getSalt()).isNotEqualTo("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365");
+    assertThat(dto.getCryptedPassword()).isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+
+    // Following fields has not changed
+    assertThat(dto.getName()).isEqualTo("Marius");
+    assertThat(dto.getScmAccounts()).contains("ma,marius33");
+    assertThat(dto.getEmail()).isEqualTo("marius@lesbronzes.fr");
+  }
+
+  @Test
+  public void associate_default_groups_when_updating_user() throws Exception {
+    db.prepareDbUnit(getClass(), "associate_default_groups_when_updating_user.xml");
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2")
+      .setScmAccounts(newArrayList("ma2")));
+    session.commit();
+
+    GroupMembershipFinder.Membership membership = groupMembershipFinder.find(GroupMembershipQuery.builder().login("marius").groupSearch("sonar-users").build());
+    assertThat(membership.groups()).hasSize(1);
+    assertThat(membership.groups().get(0).name()).isEqualTo("sonar-users");
+    assertThat(membership.groups().get(0).isMember()).isTrue();
+  }
+
+  @Test
+  public void fail_to_update_unknown_user() throws Exception {
+    try {
+      userUpdater.update(UpdateUser.create("marius")
+        .setName("Marius2")
+        .setEmail("marius2@mail.com")
+        .setPassword("password2")
+        .setPasswordConfirmation("password2")
+        .setScmAccounts(newArrayList("ma2")));
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(NotFoundException.class).hasMessage("User 'marius' does not exists");
+    }
+  }
+
+  private void createDefaultGroup() {
+    settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, "sonar-users");
+    groupDao.insert(session, new GroupDto().setName("sonar-users").setDescription("Sonar Users"));
+    session.commit();
+  }
+}
index ae92a794896b4575bd1ce626983b211e8ece43df..e9148ecb3c3942c7443251cfa950afeae8d9cbaa 100644 (file)
@@ -27,20 +27,23 @@ import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.i18n.I18n;
 import org.sonar.api.server.ws.WebService;
+import org.sonar.server.user.MockUserSession;
 import org.sonar.server.user.NewUser;
-import org.sonar.server.user.ReactivationException;
 import org.sonar.server.user.UserService;
 import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.ws.WsTester;
 
+import java.util.Locale;
 import java.util.Map;
 
 import static com.google.common.collect.Lists.newArrayList;
 import static com.google.common.collect.Maps.newHashMap;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 @RunWith(MockitoJUnitRunner.class)
 public class CreateActionTest {
@@ -52,12 +55,15 @@ public class CreateActionTest {
   @Mock
   UserService service;
 
+  @Mock
+  I18n i18n;
+
   @Captor
   ArgumentCaptor<NewUser> newUserCaptor;
 
   @Before
   public void setUp() throws Exception {
-    tester = new WsTester(new UsersWs(new CreateAction(service)));
+    tester = new WsTester(new UsersWs(new CreateAction(service, i18n), new UpdateAction(service)));
     controller = tester.controller("api/users");
   }
 
@@ -89,13 +95,10 @@ public class CreateActionTest {
     assertThat(newUserCaptor.getValue().scmAccounts()).containsOnly("jn");
     assertThat(newUserCaptor.getValue().password()).isEqualTo("1234");
     assertThat(newUserCaptor.getValue().passwordConfirmation()).isEqualTo("1234");
-    assertThat(newUserCaptor.getValue().isPreventReactivation()).isFalse();
   }
 
   @Test
-  public void return_409_when_reactive_exception() throws Exception {
-    doThrow(new ReactivationException("Already exists", "john")).when(service).create(any(NewUser.class));
-
+  public void reactivate_user() throws Exception {
     Map<String, Object> userDocMap = newHashMap();
     userDocMap.put("login", "john");
     userDocMap.put("name", "John");
@@ -105,15 +108,19 @@ public class CreateActionTest {
     userDocMap.put("createdAt", 15000L);
     userDocMap.put("updatedAt", 15000L);
     when(service.getByLogin("john")).thenReturn(new UserDoc(userDocMap));
+    when(service.create(any(NewUser.class))).thenReturn(true);
+
+    MockUserSession.set().setLogin("julien").setLocale(Locale.FRENCH);
+    when(i18n.message(Locale.FRENCH, "user.reactivated", "user.reactivated", "john")).thenReturn("The user 'john' has been reactivated.");
 
     tester.newPostRequest("api/users", "create")
       .setParam("login", "john")
-      .setParam("name", "John2")
-      .setParam("email", "john2@email.com")
-      .setParam("scm_accounts", "jn2")
-      .setParam("password", "12345")
-      .setParam("password_confirmation", "12345").execute()
-      .assertStatus(409)
-      .assertJson(getClass(), "return_409_when_reactive_exception.json");
+      .setParam("name", "John")
+      .setParam("email", "john@email.com")
+      .setParam("scm_accounts", "jn")
+      .setParam("password", "1234")
+      .setParam("password_confirmation", "1234").execute()
+      .assertJson(getClass(), "reactivate_user.json");
   }
+
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
new file mode 100644 (file)
index 0000000..7d4c7c2
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * 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.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.i18n.I18n;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.user.UpdateUser;
+import org.sonar.server.user.UserService;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.ws.WsTester;
+
+import java.util.Map;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+public class UpdateActionTest {
+
+  WebService.Controller controller;
+
+  WsTester tester;
+
+  @Mock
+  UserService service;
+
+  @Captor
+  ArgumentCaptor<UpdateUser> userCaptor;
+
+  @Before
+  public void setUp() throws Exception {
+    tester = new WsTester(new UsersWs(new CreateAction(service, mock(I18n.class)), new UpdateAction(service)));
+    controller = tester.controller("api/users");
+  }
+
+  @Test
+  public void update_user() throws Exception {
+    Map<String, Object> userDocMap = newHashMap();
+    userDocMap.put("login", "john");
+    userDocMap.put("name", "John");
+    userDocMap.put("email", "john@email.com");
+    userDocMap.put("scmAccounts", newArrayList("jn"));
+    userDocMap.put("active", true);
+    userDocMap.put("createdAt", 15000L);
+    userDocMap.put("updatedAt", 15000L);
+    when(service.getByLogin("john")).thenReturn(new UserDoc(userDocMap));
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("name", "John")
+      .setParam("email", "john@email.com")
+      .setParam("scm_accounts", "jn")
+      .setParam("password", "1234")
+      .setParam("password_confirmation", "1234")
+      .execute()
+      .assertJson(getClass(), "update_user.json");
+
+    verify(service).update(userCaptor.capture());
+    assertThat(userCaptor.getValue().login()).isEqualTo("john");
+    assertThat(userCaptor.getValue().name()).isEqualTo("John");
+    assertThat(userCaptor.getValue().email()).isEqualTo("john@email.com");
+    assertThat(userCaptor.getValue().scmAccounts()).containsOnly("jn");
+    assertThat(userCaptor.getValue().password()).isEqualTo("1234");
+    assertThat(userCaptor.getValue().passwordConfirmation()).isEqualTo("1234");
+  }
+
+  @Test
+  public void update_only_name() throws Exception {
+    Map<String, Object> userDocMap = newHashMap();
+    userDocMap.put("login", "john");
+    userDocMap.put("name", "John");
+    userDocMap.put("email", "john@email.com");
+    userDocMap.put("scmAccounts", newArrayList("jn"));
+    userDocMap.put("active", true);
+    userDocMap.put("createdAt", 15000L);
+    userDocMap.put("updatedAt", 15000L);
+    when(service.getByLogin("john")).thenReturn(new UserDoc(userDocMap));
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("name", "John")
+      .execute();
+
+    verify(service).update(userCaptor.capture());
+    assertThat(userCaptor.getValue().isNameChanged()).isTrue();
+    assertThat(userCaptor.getValue().isEmailChanged()).isFalse();
+    assertThat(userCaptor.getValue().isScmAccountsChanged()).isFalse();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isFalse();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isFalse();
+  }
+
+  @Test
+  public void update_only_email() throws Exception {
+    Map<String, Object> userDocMap = newHashMap();
+    userDocMap.put("login", "john");
+    userDocMap.put("name", "John");
+    userDocMap.put("email", "john@email.com");
+    userDocMap.put("scmAccounts", newArrayList("jn"));
+    userDocMap.put("active", true);
+    userDocMap.put("createdAt", 15000L);
+    userDocMap.put("updatedAt", 15000L);
+    when(service.getByLogin("john")).thenReturn(new UserDoc(userDocMap));
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("email", "john@email.com")
+      .execute();
+
+    verify(service).update(userCaptor.capture());
+    assertThat(userCaptor.getValue().isNameChanged()).isFalse();
+    assertThat(userCaptor.getValue().isEmailChanged()).isTrue();
+    assertThat(userCaptor.getValue().isScmAccountsChanged()).isFalse();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isFalse();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isFalse();
+  }
+
+  @Test
+  public void update_only_scm_accounts() throws Exception {
+    Map<String, Object> userDocMap = newHashMap();
+    userDocMap.put("login", "john");
+    userDocMap.put("name", "John");
+    userDocMap.put("email", "john@email.com");
+    userDocMap.put("scmAccounts", newArrayList("jn"));
+    userDocMap.put("active", true);
+    userDocMap.put("createdAt", 15000L);
+    userDocMap.put("updatedAt", 15000L);
+    when(service.getByLogin("john")).thenReturn(new UserDoc(userDocMap));
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("scm_accounts", "jn")
+      .execute();
+
+    verify(service).update(userCaptor.capture());
+    assertThat(userCaptor.getValue().isNameChanged()).isFalse();
+    assertThat(userCaptor.getValue().isEmailChanged()).isFalse();
+    assertThat(userCaptor.getValue().isScmAccountsChanged()).isTrue();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isFalse();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isFalse();
+  }
+
+  @Test
+  public void update_only_password() throws Exception {
+    Map<String, Object> userDocMap = newHashMap();
+    userDocMap.put("login", "john");
+    userDocMap.put("name", "John");
+    userDocMap.put("email", "john@email.com");
+    userDocMap.put("scmAccounts", newArrayList("jn"));
+    userDocMap.put("active", true);
+    userDocMap.put("createdAt", 15000L);
+    userDocMap.put("updatedAt", 15000L);
+    when(service.getByLogin("john")).thenReturn(new UserDoc(userDocMap));
+
+    tester.newPostRequest("api/users", "update")
+      .setParam("login", "john")
+      .setParam("password", "1234")
+      .setParam("password_confirmation", "1234")
+      .execute();
+
+    verify(service).update(userCaptor.capture());
+    assertThat(userCaptor.getValue().isNameChanged()).isFalse();
+    assertThat(userCaptor.getValue().isEmailChanged()).isFalse();
+    assertThat(userCaptor.getValue().isScmAccountsChanged()).isFalse();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isTrue();
+    assertThat(userCaptor.getValue().isPasswordChanged()).isTrue();
+  }
+}
index 70ff4320a4e5dc376061fd6916258258a0714f31..3a3290019a115dc6c0de1a8225e4a38ea35a74cf 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.user.ws;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.sonar.api.i18n.I18n;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.user.UserService;
@@ -36,7 +37,7 @@ public class UsersWsTest {
 
   @Before
   public void setUp() throws Exception {
-    WsTester tester = new WsTester(new UsersWs(new CreateAction(mock(UserService.class))));
+    WsTester tester = new WsTester(new UsersWs(new CreateAction(mock(UserService.class), mock(I18n.class)), new UpdateAction(mock(UserService.class))));
     controller = tester.controller("api/users");
   }
 
@@ -63,7 +64,7 @@ public class UsersWsTest {
     WebService.Action action = controller.action("create");
     assertThat(action).isNotNull();
     assertThat(action.isPost()).isTrue();
-    assertThat(action.params()).hasSize(7);
+    assertThat(action.params()).hasSize(6);
   }
 
   @Test
@@ -71,8 +72,7 @@ public class UsersWsTest {
     WebService.Action action = controller.action("update");
     assertThat(action).isNotNull();
     assertThat(action.isPost()).isTrue();
-    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
-    assertThat(action.params()).hasSize(4);
+    assertThat(action.params()).hasSize(6);
   }
 
   @Test
index 35c1d39c556cd33f355625cad4d0f65f9a3e8940..f6cdb3c604bb8f38c18404e12cfa6c958120eb59 100644 (file)
@@ -62,6 +62,11 @@ public class WebServiceEngineTest {
       return method;
     }
 
+    @Override
+    public boolean hasParam(String key) {
+      return params.keySet().contains(key);
+    }
+
     @Override
     protected String readParam(String key) {
       return params.get(key);
index 8737f8f2457049f4079f534ca09b47bd22f81b51..c4b55bb164f93c3ed45621928a00cf96b8b5a97a 100644 (file)
@@ -62,6 +62,11 @@ public class WsTester {
       return method;
     }
 
+    @Override
+    public boolean hasParam(String key) {
+      return params.keySet().contains(key);
+    }
+
     public TestRequest setParams(Map<String, String> m) {
       this.params = m;
       return this;
@@ -148,7 +153,6 @@ public class WsTester {
       return stream;
     }
 
-
     @Override
     public Response noContent() {
       stream().setStatus(HttpURLConnection.HTTP_NO_CONTENT);
@@ -157,7 +161,6 @@ public class WsTester {
     }
   }
 
-
   public static class Result {
     private final TestResponse response;
 
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/associate_default_groups_when_reactivating_user.xml
deleted file mode 100644 (file)
index d4b4767..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<dataset>
-
-  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[false]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
-         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
-
-  <groups id="1" name="sonar-devs" description="Sonar Devs" created_at="2014-09-08" updated_at="2014-09-08"/>
-
-  <groups_users user_id="1" group_id="1"/>
-
-</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/fail_to_create_user_if_already_exists_but_inactive.xml
deleted file mode 100644 (file)
index 24fd96b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<dataset>
-
-  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[false]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
-         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
-
-</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserCreatorTest/reactivate_user.xml
deleted file mode 100644 (file)
index 24fd96b..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<dataset>
-
-  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[false]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
-         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
-
-</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/associate_default_groups_when_reactivating_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/associate_default_groups_when_reactivating_user.xml
new file mode 100644 (file)
index 0000000..d4b4767
--- /dev/null
@@ -0,0 +1,10 @@
+<dataset>
+
+  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[false]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+
+  <groups id="1" name="sonar-devs" description="Sonar Devs" created_at="2014-09-08" updated_at="2014-09-08"/>
+
+  <groups_users user_id="1" group_id="1"/>
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/associate_default_groups_when_updating_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/associate_default_groups_when_updating_user.xml
new file mode 100644 (file)
index 0000000..d4b4767
--- /dev/null
@@ -0,0 +1,10 @@
+<dataset>
+
+  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[false]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+
+  <groups id="1" name="sonar-devs" description="Sonar Devs" created_at="2014-09-08" updated_at="2014-09-08"/>
+
+  <groups_users user_id="1" group_id="1"/>
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_reactivate_user_if_not_disabled.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_reactivate_user_if_not_disabled.xml
new file mode 100644 (file)
index 0000000..3c22b1e
--- /dev/null
@@ -0,0 +1,6 @@
+<dataset>
+
+  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[true]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/reactivate_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/reactivate_user.xml
new file mode 100644 (file)
index 0000000..24fd96b
--- /dev/null
@@ -0,0 +1,6 @@
+<dataset>
+
+  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[false]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/update_user.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/update_user.xml
new file mode 100644 (file)
index 0000000..3c22b1e
--- /dev/null
@@ -0,0 +1,6 @@
+<dataset>
+
+  <users id="101" login="marius" name="Marius" email="marius@lesbronzes.fr" active="[true]" scm_accounts="ma,marius33" created_at="1418215735482" updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+
+</dataset>
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CreateActionTest/reactivate_user.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CreateActionTest/reactivate_user.json
new file mode 100644 (file)
index 0000000..43f484c
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "user": {
+    "login": "john",
+    "name": "John",
+    "email": "john@email.com",
+    "scmAccounts": ["jn"],
+    "active": true
+  },
+  "infos": [
+    {
+      "msg": "The user 'john' has been reactivated."
+    }
+  ]
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CreateActionTest/return_409_when_reactive_exception.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/CreateActionTest/return_409_when_reactive_exception.json
deleted file mode 100644 (file)
index 0feda7c..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "user": {
-    "login": "john",
-    "name": "John",
-    "email": "john@email.com",
-    "scmAccounts": ["jn"],
-    "active": true
-  }
-}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/ws/UpdateActionTest/update_user.json b/server/sonar-server/src/test/resources/org/sonar/server/user/ws/UpdateActionTest/update_user.json
new file mode 100644 (file)
index 0000000..0feda7c
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "user": {
+    "login": "john",
+    "name": "John",
+    "email": "john@email.com",
+    "scmAccounts": ["jn"],
+    "active": true
+  }
+}
index 9373041b35165068774fc8a48e2c254217d92a81..2a3fa55a00bd468e0989fc19ea376e0d8b74d623 100644 (file)
@@ -1967,6 +1967,7 @@ events.name_required=Name (required)
 #------------------------------------------------------------------------------
 user.bad_login=Use only letters, numbers, and .-_@ please.
 user.password_doesnt_match_confirmation=Password doesn't match confirmation.
+user.reactivated=The user '{0}' has been reactivated.
 
 
 #------------------------------------------------------------------------------
index 89e920e5338856b737e859baba83c43567fcadbb..2cbd0ef0b4dee829bcce4f4dc48404660d38fc42 100644 (file)
@@ -42,6 +42,11 @@ public abstract class Request {
    */
   public abstract String method();
 
+  /**
+   * Return true of the parameter is set.
+   */
+  public abstract boolean hasParam(String key);
+
   /**
    * Returns a non-null value. To be used when parameter is required or has a default value.
    *
index 1053ebece29979d3b2744cc06853f5b7d6062461..d2ee7a65d9fff4be87615b2b2de9c29a5c53dda1 100644 (file)
@@ -42,6 +42,11 @@ public class SimpleGetRequest extends Request {
     return "GET";
   }
 
+  @Override
+  public boolean hasParam(String key) {
+    return params.keySet().contains(key);
+  }
+
   @Override
   public String param(String key) {
     return params.get(key);
index 6a86cc48e6fc9bf888be2d9579bf65748dd70c4d..ad7e00aea17545f46cd46738f1dd7bcfd151b1e2 100644 (file)
@@ -48,6 +48,11 @@ public class RequestTest {
       return "GET";
     }
 
+    @Override
+    public boolean hasParam(String key) {
+      return params.keySet().contains(key);
+    }
+
     public SimpleRequest setParam(String key, @Nullable String value) {
       if (value != null) {
         params.put(key, value);
@@ -113,6 +118,14 @@ public class RequestTest {
     request.setAction(context.controller("my_controller").action("my_action"));
   }
 
+  @Test
+  public void has_param() throws Exception {
+    request.setParam("a_required_string", "foo");
+
+    assertThat(request.hasParam("a_required_string")).isTrue();
+    assertThat(request.hasParam("unknown")).isFalse();
+  }
+
   @Test
   public void required_param_is_missing() throws Exception {
     try {
index 26be2948d03b816004a1daf022070d6c52cbd58c..4ee4a6857e758d4af955a0331b5cc6378cc07d70 100644 (file)
@@ -34,4 +34,14 @@ public class SimpleGetRequestTest {
     assertThat(request.param("foo")).isEqualTo("bar");
     assertThat(request.param("unknown")).isNull();
   }
+
+  @Test
+  public void has_param() throws Exception {
+    SimpleGetRequest request = new SimpleGetRequest();
+    assertThat(request.method()).isEqualTo("GET");
+
+    request.setParam("foo", "bar");
+    assertThat(request.hasParam("foo")).isTrue();
+    assertThat(request.hasParam("unknown")).isFalse();
+  }
 }