]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5830 Check scm account is not already used
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 6 Jan 2015 11:38:41 +0000 (12:38 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Tue, 6 Jan 2015 12:09:12 +0000 (13:09 +0100)
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/main/java/org/sonar/server/user/db/UserDao.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/user/db/UserDaoTest.java
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_create_user_when_scm_account_is_already_used.xml [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_update_user_when_scm_account_is_already_used.xml
server/sonar-server/src/test/resources/org/sonar/server/user/db/UserDaoTest/select_nullable_by_scm_account.xml [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/user/UserMapper.java
sonar-core/src/main/resources/org/sonar/core/user/UserMapper.xml
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index dd9c9c5a34514d5ede58570c9007125cb95712b6..fb59a00c93ad1a632001e62abd5af251dba7e371 100644 (file)
@@ -80,11 +80,11 @@ public class UserUpdater implements ServerComponent {
    * Return true if the user has been reactivated
    */
   public boolean create(NewUser newUser) {
-    UserDto userDto = createNewUserDto(newUser);
     boolean isUserReactivated = false;
 
     DbSession dbSession = dbClient.openSession(false);
     try {
+      UserDto userDto = createNewUserDto(dbSession, newUser);
       String login = userDto.getLogin();
       UserDto existingUser = dbClient.userDao().selectNullableByLogin(dbSession, login);
       if (existingUser == null) {
@@ -99,7 +99,7 @@ public class UserUpdater implements ServerComponent {
           .setScmAccounts(newUser.scmAccounts())
           .setPassword(newUser.password())
           .setPasswordConfirmation(newUser.passwordConfirmation());
-        updateUserDto(updateUser, existingUser);
+        updateUserDto(dbSession, updateUser, existingUser);
         updateUser(dbSession, existingUser);
         isUserReactivated = true;
       }
@@ -116,7 +116,7 @@ public class UserUpdater implements ServerComponent {
     try {
       UserDto user = dbClient.userDao().selectNullableByLogin(dbSession, updateUser.login());
       if (user != null) {
-        updateUserDto(updateUser, user);
+        updateUserDto(dbSession, updateUser, user);
         updateUser(dbSession, user);
       } else {
         throw new NotFoundException(String.format("User '%s' does not exists", updateUser.login()));
@@ -128,7 +128,7 @@ public class UserUpdater implements ServerComponent {
     }
   }
 
-  private static UserDto createNewUserDto(NewUser newUser) {
+  private UserDto createNewUserDto(DbSession dbSession, NewUser newUser) {
     UserDto userDto = new UserDto();
     List<Message> messages = newArrayList();
 
@@ -151,7 +151,9 @@ public class UserUpdater implements ServerComponent {
     validatePasswords(password, passwordConfirmation, messages);
     setEncryptedPassWord(password, userDto);
 
-    userDto.setScmAccounts(convertScmAccountsToCsv(newUser.scmAccounts()));
+    List<String> scmAccounts = sanitizeScmAccounts(newUser.scmAccounts());
+    validateScmAccounts(dbSession, scmAccounts, login, email, messages);
+    userDto.setScmAccounts(convertScmAccountsToCsv(scmAccounts));
 
     if (!messages.isEmpty()) {
       throw new BadRequestException(messages);
@@ -159,7 +161,7 @@ public class UserUpdater implements ServerComponent {
     return userDto;
   }
 
-  private static void updateUserDto(UpdateUser updateUser, UserDto userDto) {
+  private void updateUserDto(DbSession dbSession, UpdateUser updateUser, UserDto userDto) {
     List<Message> messages = newArrayList();
 
     String name = updateUser.name();
@@ -182,7 +184,9 @@ public class UserUpdater implements ServerComponent {
     }
 
     if (updateUser.isScmAccountsChanged()) {
-      userDto.setScmAccounts(convertScmAccountsToCsv(updateUser.scmAccounts()));
+      List<String> scmAccounts = sanitizeScmAccounts(updateUser.scmAccounts());
+      validateScmAccounts(dbSession, scmAccounts, userDto.getLogin(), email != null ? email : userDto.getEmail(), messages);
+      userDto.setScmAccounts(convertScmAccountsToCsv(scmAccounts));
     }
 
     if (!messages.isEmpty()) {
@@ -230,6 +234,30 @@ public class UserUpdater implements ServerComponent {
     }
   }
 
+  private void validateScmAccounts(DbSession dbSession, @Nullable List<String> scmAccounts, @Nullable String login, @Nullable String email, List<Message> messages) {
+    if (scmAccounts != null) {
+      for (String scmAccount : scmAccounts) {
+        if (scmAccount.equals(login) || scmAccount.equals(email)) {
+          messages.add(Message.of("user.login_or_email_used_as_scm_account"));
+        } else {
+          UserDto matchingUser = dbClient.userDao().selectNullableByScmAccountOrLoginOrName(dbSession, scmAccount);
+          if (matchingUser != null) {
+            messages.add(Message.of("user.scm_account_already_used", scmAccount, matchingUser.getName(), matchingUser.getLogin()));
+          }
+        }
+      }
+    }
+  }
+
+  @CheckForNull
+  private static List<String> sanitizeScmAccounts(@Nullable List<String> scmAccounts) {
+    if (scmAccounts != null) {
+      scmAccounts.removeAll(Arrays.asList(null, ""));
+      return scmAccounts;
+    }
+    return null;
+  }
+
   private void saveUser(DbSession dbSession, UserDto userDto) {
     long now = system2.now();
     userDto.setActive(true).setCreatedAt(now).setUpdatedAt(now);
@@ -256,8 +284,6 @@ public class UserUpdater implements ServerComponent {
   @CheckForNull
   private static String convertScmAccountsToCsv(@Nullable List<String> scmAccounts) {
     if (scmAccounts != null) {
-      // Remove empty characters
-      scmAccounts.removeAll(Arrays.asList(null, ""));
       // Add one empty character at the begin and at the end of the list to be able to generate a coma at the begin and at the end of the
       // text
       scmAccounts.add(0, "");
index 977c3c9cf44746de6ce0be1b4d9aa942bb1a61da..a43857558614b00487c8c22644cd373c3fbc00ea 100644 (file)
@@ -49,6 +49,11 @@ public class UserDao extends org.sonar.core.user.UserDao implements DaoComponent
     return user;
   }
 
+  @CheckForNull
+  public UserDto selectNullableByScmAccountOrLoginOrName(DbSession session, String scmAccount) {
+    return mapper(session).selectNullableByScmAccountOrLoginOrName(scmAccount);
+  }
+
   protected UserMapper mapper(DbSession session) {
     return session.getMapper(UserMapper.class);
   }
index cf7a400e93d6c3bf14ea9fdca73d8f3269edb096..ad64fcda6b3675f56af8713390b0fa11ff45229c 100644 (file)
@@ -321,6 +321,56 @@ public class UserUpdaterTest {
     }
   }
 
+  @Test
+  public void fail_to_create_user_when_scm_account_is_already_used() throws Exception {
+    db.prepareDbUnit(getClass(), "fail_to_create_user_when_scm_account_is_already_used.xml");
+
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius2")
+        .setEmail("marius2@mail.com")
+        .setPassword("password2")
+        .setPasswordConfirmation("password2")
+        .setScmAccounts(newArrayList("jo")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.scm_account_already_used", "jo", "John", "john"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_when_scm_account_is_user_login() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius2")
+        .setEmail("marius2@mail.com")
+        .setPassword("password2")
+        .setPasswordConfirmation("password2")
+        .setScmAccounts(newArrayList("marius")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.login_or_email_used_as_scm_account"));
+    }
+  }
+
+  @Test
+  public void fail_to_create_user_when_scm_account_is_user_email() throws Exception {
+    try {
+      userUpdater.create(NewUser.create()
+        .setLogin("marius")
+        .setName("Marius2")
+        .setEmail("marius2@mail.com")
+        .setPassword("password2")
+        .setPasswordConfirmation("password2")
+        .setScmAccounts(newArrayList("marius2@mail.com")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.login_or_email_used_as_scm_account"));
+    }
+  }
+
   @Test
   public void notify_new_user() throws Exception {
     createDefaultGroup();
@@ -466,6 +516,24 @@ public class UserUpdaterTest {
     assertThat(dto.getUpdatedAt()).isEqualTo(1418215735486L);
   }
 
+  @Test
+  public void update_user_with_scm_accounts_containing_blank_entry() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    userUpdater.update(UpdateUser.create("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .setPasswordConfirmation("password2")
+      .setScmAccounts(newArrayList("ma2", "", null)));
+    session.commit();
+    session.clearCache();
+
+    UserDto dto = userDao.selectNullableByLogin(session, "marius");
+    assertThat(dto.getScmAccounts()).isEqualTo(",ma2,");
+  }
+
   @Test
   public void update_only_user_name() throws Exception {
     db.prepareDbUnit(getClass(), "update_user.xml");
@@ -595,6 +663,67 @@ public class UserUpdaterTest {
     }
   }
 
+  @Test
+  public void fail_to_update_user_when_scm_account_is_already_used() throws Exception {
+    db.prepareDbUnit(getClass(), "fail_to_update_user_when_scm_account_is_already_used.xml");
+    createDefaultGroup();
+
+    try {
+      userUpdater.update(UpdateUser.create("marius")
+        .setName("Marius2")
+        .setEmail("marius2@mail.com")
+        .setPassword("password2")
+        .setPasswordConfirmation("password2")
+        .setScmAccounts(newArrayList("jo")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.scm_account_already_used", "jo", "John", "john"));
+    }
+  }
+
+  @Test
+  public void fail_to_update_user_when_scm_account_is_user_login() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    try {
+      userUpdater.update(UpdateUser.create("marius")
+        .setScmAccounts(newArrayList("marius")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.login_or_email_used_as_scm_account"));
+    }
+  }
+
+  @Test
+  public void fail_to_update_user_when_scm_account_is_existing_user_email() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    try {
+      userUpdater.update(UpdateUser.create("marius")
+        .setScmAccounts(newArrayList("marius@lesbronzes.fr")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.login_or_email_used_as_scm_account"));
+    }
+  }
+
+  @Test
+  public void fail_to_update_user_when_scm_account_is_new_user_email() throws Exception {
+    db.prepareDbUnit(getClass(), "update_user.xml");
+    createDefaultGroup();
+
+    try {
+      userUpdater.update(UpdateUser.create("marius")
+        .setEmail("marius@newmail.com")
+        .setScmAccounts(newArrayList("marius@newmail.com")));
+      fail();
+    } catch (BadRequestException e) {
+      assertThat(e.errors().messages()).containsOnly(Message.of("user.login_or_email_used_as_scm_account"));
+    }
+  }
+
   private void createDefaultGroup() {
     settings.setProperty(CoreProperties.CORE_DEFAULT_GROUP, "sonar-users");
     groupDao.insert(session, new GroupDto().setName("sonar-users").setDescription("Sonar Users"));
index 67534bdfe878344f63449df5821b7c08873b438c..054d320e1e6f7f011b5b48667f2756070555ab2b 100644 (file)
@@ -71,6 +71,18 @@ public class UserDaoTest {
     assertThat(dto.getUpdatedAt()).isEqualTo(1418215735485L);
   }
 
+  @Test
+  public void select_nullable_by_scm_account() {
+    db.prepareDbUnit(getClass(), "select_nullable_by_scm_account.xml");
+
+    assertThat(dao.selectNullableByScmAccountOrLoginOrName(session, "ma").getLogin()).isEqualTo("marius");
+    assertThat(dao.selectNullableByScmAccountOrLoginOrName(session, "marius").getLogin()).isEqualTo("marius");
+    assertThat(dao.selectNullableByScmAccountOrLoginOrName(session, "marius@lesbronzes.fr").getLogin()).isEqualTo("marius");
+
+    assertThat(dao.selectNullableByScmAccountOrLoginOrName(session, "m")).isNull();
+    assertThat(dao.selectNullableByScmAccountOrLoginOrName(session, "unknown")).isNull();
+  }
+
   @Test
   public void select_by_login_with_unknown_login() {
     try {
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_create_user_when_scm_account_is_already_used.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/UserUpdaterTest/fail_to_create_user_when_scm_account_is_already_used.xml
new file mode 100644 (file)
index 0000000..5a257af
--- /dev/null
@@ -0,0 +1,6 @@
+<dataset>
+
+  <users id="101" login="john" name="John" email="john@email.com" active="[true]" scm_accounts=",jo," created_at="1418215735482" updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+
+</dataset>
index 5a257afb338968be42dc7ead03002edf42499a39..d535e5bf01d7d45c82b7d4ee7bf8b5cd34591c18 100644 (file)
@@ -2,5 +2,7 @@
 
   <users id="101" login="john" name="John" email="john@email.com" active="[true]" scm_accounts=",jo," created_at="1418215735482" updated_at="1418215735485"
          salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"/>
+  <users id="102" 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/db/UserDaoTest/select_nullable_by_scm_account.xml b/server/sonar-server/src/test/resources/org/sonar/server/user/db/UserDaoTest/select_nullable_by_scm_account.xml
new file mode 100644 (file)
index 0000000..854325a
--- /dev/null
@@ -0,0 +1,9 @@
+<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"/>
+  <users id="102" login="sbrandhof" name="Simon Brandhof" email="sbrandhof@lesbronzes.fr" active="[true]" scm_accounts="[null]" created_at="1418215735482"
+         updated_at="1418215735485"
+         salt="79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8366" crypted_password="650d2261c98361e2f67f90ce5c65a95e7d8ea2fh"/>
+
+</dataset>
index 329fd09beeb58e4d4a21a89a41bd1ff5375f9f35..48fda4f8dc81de978014fadab28f07476f19cf93 100644 (file)
@@ -31,6 +31,9 @@ public interface UserMapper {
   @CheckForNull
   UserDto selectByLogin(String login);
 
+  @CheckForNull
+  UserDto selectNullableByScmAccountOrLoginOrName(String scmAccount);
+
   @CheckForNull
   UserDto selectUser(long userId);
 
index d505e1c742e1eb0abd203d520ae52fb1f553ede7..a50be05abc98dfe4de2ae49db0d54ec70d9be5e3 100644 (file)
   <select id="selectByLogin" parameterType="String" resultType="User">
     SELECT
     <include refid="userColumns"/>
-    FROM users u WHERE u.login=#{login}
+    FROM users u
+    WHERE u.login=#{login}
+  </select>
+
+  <select id="selectNullableByScmAccountOrLoginOrName" parameterType="String" resultType="User">
+    SELECT
+    <include refid="userColumns"/>
+    FROM users u
+    WHERE
+    u.login=#{scmAccount}
+    OR u.email=#{scmAccount}
+    OR
+    <choose>
+      <when test="_databaseId == 'mssql'">
+        u.scm_accounts LIKE '%,' + #{scmAccount} + ',%'
+      </when>
+      <when test="_databaseId == 'mysql'">
+        u.scm_accounts LIKE concat('%,', #{scmAccount}, ',%')
+      </when>
+      <otherwise>
+        u.scm_accounts LIKE '%,' || #{scmAccount} || ',%'
+      </otherwise>
+    </choose>
   </select>
 
   <select id="selectUser" parameterType="long" resultType="User">
     SELECT
     <include refid="userColumns"/>
-    FROM users u WHERE u.id=#{id}
+    FROM users u
+    WHERE u.id=#{id}
   </select>
 
   <select id="selectUserByLogin" parameterType="string" resultType="User">
     SELECT
     <include refid="userColumns"/>
-    FROM users u WHERE u.login=#{id} AND u.active=${_true}
+    FROM users u
+    WHERE u.login=#{id} AND u.active=${_true}
   </select>
 
   <select id="selectUsersByLogins" parameterType="map" resultType="User">
index d701500fbd3ce0406bfbd92049a4b35b10bb62f5..13eaa3d208f8cce141d083ae55f50621620de99a 100644 (file)
@@ -1984,6 +1984,8 @@ 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.
 user.add_scm_account=Add SCM account
+user.scm_account_already_used=The scm account '{0}' is already used by user '{1} ({2})'
+user.login_or_email_used_as_scm_account=Login and email are automatically considered as SCM accounts
 
 
 #------------------------------------------------------------------------------