]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10599 Synchronize login during authentication
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Mon, 23 Apr 2018 14:41:34 +0000 (16:41 +0200)
committerSonarTech <sonartech@sonarsource.com>
Wed, 23 May 2018 18:20:46 +0000 (20:20 +0200)
109 files changed:
server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMemberMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDbTester.java
server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationMemberDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java
server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml [deleted file]
server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions.xml [deleted file]
server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml [deleted file]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsers.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsers.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullable.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsers.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsers.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsers.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest/users.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest/users.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest/users.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest/users.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest/users.sql [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest/users.sql [new file with mode: 0644]
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/UserTester.java
server/sonar-server/src/main/java/org/sonar/server/authentication/EmailAlreadyExistsException.java
server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationFactory.java
server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java
server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserDoc.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexDefinition.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexer.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java
server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java
server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ScmAccountToUserLoaderTest.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/MetricsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/organization/ws/RemoveMemberActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/DeprecatedUserFinderTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ExternalIdentityTest.java
server/sonar-server/src/test/java/org/sonar/server/user/NewUserTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexerTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/UserIdentity.java
sonar-plugin-api/src/test/java/org/sonar/api/server/authentication/UserIdentityTest.java
tests/plugins/base-auth-plugin/src/main/java/FakeBaseIdProvider.java
tests/plugins/oauth2-auth-plugin/src/main/java/FakeOAuth2IdProvider.java
tests/src/test/java/org/sonarqube/tests/Category6Suite.java
tests/src/test/java/org/sonarqube/tests/user/BaseIdentityProviderTest.java
tests/src/test/java/org/sonarqube/tests/user/OAuth2IdentityProviderTest.java
tests/src/test/java/org/sonarqube/tests/user/OrganizationIdentityProviderTest.java
tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java
tests/src/test/java/org/sonarqube/tests/user/SonarCloudUserSuite.java
tests/src/test/java/org/sonarqube/tests/user/UsersPageTest.java

index 95b8cf51d7b3b81328c5b0d71cbefe568ef66d14..48d8e16cc66f8d9d111727908bce9e0e0309a33b 100644 (file)
@@ -1,4 +1,4 @@
-INSERT INTO USERS(ID, UUID, LOGIN, NAME, EMAIL, EXTERNAL_IDENTITY, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, HASH_METHOD, IS_ROOT, ONBOARDED, CREATED_AT, UPDATED_AT) VALUES (1, 'UuidnciQUUs7Zd3KPvFD', 'admin', 'Administrator', '', 'admin', 'sonarqube', true, '$2a$12$uCkkXmhW5ThVK8mpBvnXOOJRLd64LJeHTeCkSuB3lfaR2N0AYBaSi', null, 'BCRYPT', false, false, '1418215735482', '1418215735482');
+INSERT INTO USERS(ID, UUID, LOGIN, NAME, EMAIL, EXTERNAL_ID, EXTERNAL_LOGIN, EXTERNAL_IDENTITY_PROVIDER, USER_LOCAL, CRYPTED_PASSWORD, SALT, HASH_METHOD, IS_ROOT, ONBOARDED, CREATED_AT, UPDATED_AT) VALUES (1, 'UuidnciQUUs7Zd3KPvFD', 'admin', 'Administrator', '', 'admin', 'admin', 'sonarqube', true, '$2a$12$uCkkXmhW5ThVK8mpBvnXOOJRLd64LJeHTeCkSuB3lfaR2N0AYBaSi', null, 'BCRYPT', false, false, '1418215735482', '1418215735482');
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
 
 INSERT INTO GROUPS(ID, ORGANIZATION_UUID, NAME, DESCRIPTION, CREATED_AT, UPDATED_AT) VALUES (1, 'AVdqnciQUUs7Zd3KPvFD', 'sonar-administrators', 'System administrators', '2011-09-26 22:27:51.0', '2011-09-26 22:27:51.0');
index 46b8deff215dad7655f9fa058b1a363e9b3c30d8..896cadc48bb16c164369fb476f2d8645b25ddbc1 100644 (file)
@@ -454,8 +454,8 @@ CREATE INDEX "IX_ARP_ON_ACTIVE_RULE_ID" ON "ACTIVE_RULE_PARAMETERS" ("ACTIVE_RUL
 
 CREATE TABLE "USERS" (
   "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
-  "UUID" VARCHAR(40),
-  "LOGIN" VARCHAR(255),
+  "UUID" VARCHAR(40) NOT NULL,
+  "LOGIN" VARCHAR(255) NOT NULL,
   "NAME" VARCHAR(200),
   "EMAIL" VARCHAR(100),
   "CRYPTED_PASSWORD" VARCHAR(100),
@@ -463,8 +463,9 @@ CREATE TABLE "USERS" (
   "HASH_METHOD" VARCHAR(10),
   "ACTIVE" BOOLEAN DEFAULT TRUE,
   "SCM_ACCOUNTS" VARCHAR(4000),
-  "EXTERNAL_IDENTITY" VARCHAR(255),
-  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "EXTERNAL_ID" VARCHAR(255) NOT NULL,
+  "EXTERNAL_LOGIN" VARCHAR(255) NOT NULL,
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100) NOT NULL,
   "IS_ROOT" BOOLEAN NOT NULL,
   "USER_LOCAL" BOOLEAN,
   "ONBOARDED" BOOLEAN NOT NULL,
@@ -473,7 +474,9 @@ CREATE TABLE "USERS" (
   "HOMEPAGE_TYPE" VARCHAR(40),
   "HOMEPAGE_PARAMETER" VARCHAR(40)
 );
+CREATE UNIQUE INDEX "USERS_UUID" ON "USERS" ("UUID");
 CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE UNIQUE INDEX "UNIQ_EXTERNAL_ID" ON "USERS" ("EXTERNAL_IDENTITY_PROVIDER", "EXTERNAL_ID");
 CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
 
 
index cd7cb05eca56bf2c4be9e449c1afc2c107d9a302..3762ca7b575022e3e1ec22cca63adc480615ba32 100644 (file)
@@ -38,8 +38,8 @@ public class OrganizationMemberDao implements Dao {
     return Optional.ofNullable(mapper(dbSession).select(organizationUuid, userId));
   }
 
-  public List<String> selectLoginsByOrganizationUuid(DbSession dbSession, String organizationUuid) {
-    return mapper(dbSession).selectLogins(organizationUuid);
+  public List<String> selectUserUuidsByOrganizationUuid(DbSession dbSession, String organizationUuid) {
+    return mapper(dbSession).selectUserUuids(organizationUuid);
   }
 
   public List<Integer> selectUserIdsByOrganizationUuid(DbSession dbSession, String organizationUuid) {
@@ -67,20 +67,19 @@ public class OrganizationMemberDao implements Dao {
   }
 
   /**
-   *
-   * @param loginOrganizationConsumer {@link BiConsumer}<String,String> (login, organization uuid)
+   * @param userUuidOrganizationConsumer {@link BiConsumer}<String,String> (uuid, organization uuid)
    */
-  public void selectForUserIndexing(DbSession dbSession, Collection<String> logins, BiConsumer<String, String> loginOrganizationConsumer) {
-    executeLargeInputsWithoutOutput(logins, list -> mapper(dbSession).selectForIndexing(list)
-      .forEach(row -> loginOrganizationConsumer.accept(row.get("login"), row.get("organizationUuid"))));
+  public void selectForUserIndexing(DbSession dbSession, Collection<String> uuids, BiConsumer<String, String> userUuidOrganizationConsumer) {
+    executeLargeInputsWithoutOutput(uuids, list -> mapper(dbSession).selectForIndexing(list)
+      .forEach(row -> userUuidOrganizationConsumer.accept(row.get("uuid"), row.get("organizationUuid"))));
   }
 
   /**
    *
-   * @param loginOrganizationConsumer {@link BiConsumer}<String,String> (login, organization uuid)
+   * @param userUuidOrganizationConsumer {@link BiConsumer}<String,String> (uuid, organization uuid)
    */
-  public void selectAllForUserIndexing(DbSession dbSession, BiConsumer<String, String> loginOrganizationConsumer) {
+  public void selectAllForUserIndexing(DbSession dbSession, BiConsumer<String, String> userUuidOrganizationConsumer) {
     mapper(dbSession).selectAllForIndexing()
-      .forEach(row -> loginOrganizationConsumer.accept(row.get("login"), row.get("organizationUuid")));
+      .forEach(row -> userUuidOrganizationConsumer.accept(row.get("uuid"), row.get("organizationUuid")));
   }
 }
index 15ab9922f9456f2ab65bb99a9036e2509f797df7..defe8395dd2df3238968974b7421624134f5e717 100644 (file)
@@ -29,11 +29,11 @@ public interface OrganizationMemberMapper {
 
   Set<String> selectOrganizationUuidsByUser(@Param("userId") int userId);
 
-  List<String> selectLogins(String organizationUuid);
+  List<String> selectUserUuids(String organizationUuid);
 
   List<Integer> selectUserIds(String organizationUuid);
 
-  List<Map<String, String>> selectForIndexing(@Param("logins") List<String> logins);
+  List<Map<String, String>> selectForIndexing(@Param("uuids") List<String> uuids);
 
   List<Map<String, String>> selectAllForIndexing();
 
index d50118a5db7dd0ea3b1864ce74638c9cf5a1d4fd..c892dc9c205ffffcd75845c35e40dfefd9940c57 100644 (file)
@@ -161,6 +161,10 @@ public class PropertiesDao implements Dao {
     return executeLargeInputs(componentIds, getMapper(session)::selectByComponentIds);
   }
 
+  public List<PropertyDto> selectByKeyAndMatchingValue(DbSession session, String key, String value) {
+    return getMapper(session).selectByKeyAndMatchingValue(key, value);
+  }
+
   /**
    * Saves the specified property and its value.
    * <p>
index 0e50bac5528afc4af4c7b09aee4cdc8c6fd6e81c..d2d181739e22c4f2594cf64a57df2e3ebee82325 100644 (file)
@@ -42,6 +42,8 @@ public interface PropertiesMapper {
 
   List<PropertyDto> selectByQuery(@Param("query") PropertyQuery query);
 
+  List<PropertyDto> selectByKeyAndMatchingValue(@Param("key") String key, @Param("value") String value);
+
   List<PropertyDto> selectDescendantModuleProperties(@Param("moduleUuid") String moduleUuid, @Param(value = "scope") String scope,
     @Param(value = "excludeDisabled") boolean excludeDisabled);
 
index 73c04c6e179559bfdc8b7d7f53a057ccec92f769..4bd8de97b522514f602f1848840eef23b25cfb65 100644 (file)
@@ -58,6 +58,11 @@ public class UserDao implements Dao {
     return mapper(session).selectUser(userId);
   }
 
+  @CheckForNull
+  public UserDto selectByUuid(DbSession session, String uuid) {
+    return mapper(session).selectByUuid(uuid);
+  }
+
   /**
    * Select users by ids, including disabled users. An empty list is returned
    * if list of ids is empty, without any db round trips.
@@ -82,6 +87,14 @@ public class UserDao implements Dao {
     return executeLargeInputs(logins, mapper(session)::selectByLogins);
   }
 
+  /**
+   * Select users by uuids, including disabled users. An empty list is returned
+   * if list of uuids is empty, without any db round trips.
+   */
+  public List<UserDto> selectByUuids(DbSession session, Collection<String> uuids) {
+    return executeLargeInputs(uuids, mapper(session)::selectByUuids);
+  }
+
   /**
    * Gets a list users by their logins. The result does NOT contain {@code null} values for users not found, so
    * the size of result may be less than the number of keys.
@@ -165,12 +178,17 @@ public class UserDao implements Dao {
     return mapper(dbSession).selectByEmail(email.toLowerCase(Locale.ENGLISH));
   }
 
-  public void scrollByLogins(DbSession dbSession, Collection<String> logins, Consumer<UserDto> consumer) {
+  @CheckForNull
+  public UserDto selectByExternalIdAndIdentityProvider(DbSession dbSession, String externalId, String externalIdentityProvider) {
+    return mapper(dbSession).selectByExternalIdAndIdentityProvider(externalId, externalIdentityProvider);
+  }
+
+  public void scrollByUuids(DbSession dbSession, Collection<String> uuids, Consumer<UserDto> consumer) {
     UserMapper mapper = mapper(dbSession);
 
-    executeLargeInputsWithoutOutput(logins,
-      pageOfLogins -> mapper
-        .selectByLogins(pageOfLogins)
+    executeLargeInputsWithoutOutput(uuids,
+      pageOfUuids -> mapper
+        .selectByUuids(pageOfUuids)
         .forEach(consumer));
   }
 
index 5c2cd93a2f36342f2e389bb65dee6a3aa289a21d..129d636730c7cf02f5a58d7ea7f1b3a362b8c041 100644 (file)
@@ -41,7 +41,8 @@ public class UserDto {
   private String email;
   private boolean active = true;
   private String scmAccounts;
-  private String externalIdentity;
+  private String externalId;
+  private String externalLogin;
   private String externalIdentityProvider;
   // Hashed password that may be null in case of external authentication
   private String cryptedPassword;
@@ -151,12 +152,21 @@ public class UserDto {
     }
   }
 
-  public String getExternalIdentity() {
-    return externalIdentity;
+  public String getExternalId() {
+    return externalId;
   }
 
-  public UserDto setExternalIdentity(String authorithy) {
-    this.externalIdentity = authorithy;
+  public UserDto setExternalId(String externalId) {
+    this.externalId = externalId;
+    return this;
+  }
+
+  public String getExternalLogin() {
+    return externalLogin;
+  }
+
+  public UserDto setExternalLogin(String externalLogin) {
+    this.externalLogin = externalLogin;
     return this;
   }
 
index e02486dce16088d68280ebf1e49706a8fdc12ea5..c83c98a739a8e4d430b7ec88f58fd088013439ce 100644 (file)
@@ -27,6 +27,9 @@ import org.sonar.api.user.UserQuery;
 
 public interface UserMapper {
 
+  @CheckForNull
+  UserDto selectByUuid(String uuid);
+
   @CheckForNull
   UserDto selectByLogin(String login);
 
@@ -50,11 +53,16 @@ public interface UserMapper {
 
   List<UserDto> selectByLogins(List<String> logins);
 
+  List<UserDto> selectByUuids(List<String> uuids);
+
   List<UserDto> selectByIds(@Param("ids") List<Integer> ids);
 
   @CheckForNull
   UserDto selectByEmail(String email);
 
+  @CheckForNull
+  UserDto selectByExternalIdAndIdentityProvider(@Param("externalId") String externalId, @Param("externalIdentityProvider") String externalExternalIdentityProvider);
+
   void scrollAll(ResultHandler<UserDto> handler);
 
   /**
index 49ca6e65af590d44e2d5fb58b2da7c7c7d4d0921..68e3767c9e4e94405ec6478440fb61732f79b1fb 100644 (file)
@@ -17,8 +17,8 @@
     and om.user_id = #{userId, jdbcType=INTEGER}
   </select>
 
-  <select id="selectLogins" resultType="string">
-    select u.login
+  <select id="selectUserUuids" resultType="string">
+    select u.uuid
     from organization_members om
       inner join users u on om.user_id = u.id
     where om.organization_uuid=#{organizationUuid,jdbcType=VARCHAR}
   </select>
 
   <select id="selectForIndexing" resultType="hashmap">
-    select u.login as "login", om.organization_uuid as "organizationUuid"
+    select u.uuid as "uuid", om.organization_uuid as "organizationUuid"
     from organization_members om
      inner join users u on om.user_id=u.id
-    where u.login in
-    <foreach collection="logins" open="(" close=")" item="login" separator=",">
-      #{login, jdbcType=VARCHAR}
+    where u.uuid in
+    <foreach collection="uuids" open="(" close=")" item="uuid" separator=",">
+      #{uuid, jdbcType=VARCHAR}
     </foreach>
   </select>
 
   <select id="selectAllForIndexing" resultType="hashmap">
-    select u.login as "login", om.organization_uuid as "organizationUuid"
+    select u.uuid as "uuid", om.organization_uuid as "organizationUuid"
     from organization_members om
     inner join users u on om.user_id=u.id
   </select>
index eb6f2943942ac7cbfb6aa9138bb01ec06885cee8..246bc8090d1e6120b4502fac21daf9a1787845ff 100644 (file)
     and ps.organization_uuid=#{organizationUuid,jdbcType=VARCHAR}
   </select>
 
+  <select id="selectByKeyAndMatchingValue" parameterType="map" resultType="ScrapProperty">
+    select
+      <include refid="columnsToScrapPropertyDto"/>
+    from properties p
+    <where>
+      p.prop_key = #{key,jdbcType=VARCHAR}
+      and p.text_value like #{value,jdbcType=VARCHAR}
+    </where>
+  </select>
+
   <insert id="insertAsEmpty" parameterType="Map" useGeneratedKeys="false">
     insert into properties
     (
index 5626e1c4c689d975d19ec5df86a7127b54dc5f42..f28b9383fdd03c944b36e0c1ccdcb12c0fcc0e5d 100644 (file)
@@ -14,7 +14,8 @@
         u.salt as "salt",
         u.crypted_password as "cryptedPassword",
         u.hash_method as "hashMethod",
-        u.external_identity as "externalIdentity",
+        u.external_id as "externalId",
+        u.external_login as "externalLogin",
         u.external_identity_provider as "externalIdentityProvider",
         u.user_local as "local",
         u.is_root as "root",
         u.homepage_parameter as "homepageParameter"
     </sql>
 
+    <select id="selectByUuid" parameterType="String" resultType="User">
+        SELECT
+        <include refid="userColumns"/>
+        FROM users u
+        WHERE u.uuid=#{uuid}
+    </select>
+
     <select id="selectByLogin" parameterType="String" resultType="User">
         SELECT
         <include refid="userColumns"/>
         </foreach>
     </select>
 
+    <select id="selectByUuids" parameterType="string" resultType="User">
+        SELECT
+        <include refid="userColumns"/>
+        FROM users u
+        WHERE u.uuid in
+        <foreach collection="list" open="(" close=")" item="uuid" separator=",">
+            #{uuid}
+        </foreach>
+    </select>
+
     <select id="scrollAll" resultType="User" fetchSize="${_scrollFetchSize}" resultSetType="FORWARD_ONLY">
         select
         <include refid="userColumns"/>
         AND u.active=${_true}
     </select>
 
+    <select id="selectByExternalIdAndIdentityProvider" parameterType="map" resultType="User">
+        SELECT
+        <include refid="userColumns"/>
+        FROM users u
+        WHERE u.external_id=#{externalId} AND u.external_identity_provider=#{externalIdentityProvider}
+    </select>
+
     <select id="countRootUsersButLogin" parameterType="String" resultType="long">
         select
         count(1)
         active = ${_false},
         email = null,
         scm_accounts = null,
-        external_identity = null,
-        external_identity_provider = null,
         salt = null,
         crypted_password = null,
         updated_at = #{now, jdbcType=BIGINT}
         email,
         active,
         scm_accounts,
-        external_identity,
+        external_id,
+        external_login,
         external_identity_provider,
         user_local,
         salt,
         #{user.email,jdbcType=VARCHAR},
         #{user.active,jdbcType=BOOLEAN},
         #{user.scmAccounts,jdbcType=VARCHAR},
-        #{user.externalIdentity,jdbcType=VARCHAR},
+        #{user.externalId,jdbcType=VARCHAR},
+        #{user.externalLogin,jdbcType=VARCHAR},
         #{user.externalIdentityProvider,jdbcType=VARCHAR},
         #{user.local,jdbcType=BOOLEAN},
         #{user.salt,jdbcType=VARCHAR},
 
     <update id="update" parameterType="map">
         update users set
+        login = #{user.login, jdbcType=VARCHAR},
         name = #{user.name, jdbcType=VARCHAR},
         email = #{user.email, jdbcType=VARCHAR},
         active = #{user.active, jdbcType=BOOLEAN},
         scm_accounts = #{user.scmAccounts, jdbcType=VARCHAR},
-        external_identity = #{user.externalIdentity, jdbcType=VARCHAR},
+        external_id = #{user.externalId, jdbcType=VARCHAR},
+        external_login = #{user.externalLogin, jdbcType=VARCHAR},
         external_identity_provider = #{user.externalIdentityProvider, jdbcType=VARCHAR},
         user_local = #{user.local, jdbcType=BOOLEAN},
         onboarded = #{user.onboarded, jdbcType=BOOLEAN},
         homepage_type = #{user.homepageType, jdbcType=VARCHAR},
         homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR}
         where
-        login = #{user.login, jdbcType=VARCHAR}
+        uuid = #{user.uuid, jdbcType=VARCHAR}
     </update>
 
 </mapper>
index d2d7c54ff0c0ac63e2eb769926c4bbbf03457d05..6aadd6dd35673013b3146a9739b5555473418cd2 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.organization;
 
+import java.util.Arrays;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
 import org.sonar.db.DbSession;
@@ -87,9 +88,8 @@ public class OrganizationDbTester {
     dbSession.commit();
   }
 
-  public void addMember(OrganizationDto organization, UserDto user) {
-    checkArgument(user.getId() != null, "User must be saved in database");
-    dbTester.getDbClient().organizationMemberDao().insert(dbTester.getSession(), new OrganizationMemberDto().setOrganizationUuid(organization.getUuid()).setUserId(user.getId()));
+  public void addMember(OrganizationDto organization, UserDto... users) {
+    Arrays.stream(users).forEach(u -> dbTester.getDbClient().organizationMemberDao().insert(dbTester.getSession(), new OrganizationMemberDto().setOrganizationUuid(organization.getUuid()).setUserId(u.getId())));
     dbTester.commit();
   }
 
index a9cfa39da00a1a8aec95d0a56d2c94cd3e6941c3..9b2311493bfe798678b5910c565f492df10010a6 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.db.organization;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -34,6 +33,7 @@ import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.UserDto;
 
+import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.assertj.core.api.Assertions.tuple;
@@ -62,7 +62,7 @@ public class OrganizationMemberDaoTest {
   }
 
   @Test
-  public void select_logins() {
+  public void select_user_uuids_by_organization_uuid() {
     OrganizationDto organization = db.organizations().insert();
     OrganizationDto anotherOrganization = db.organizations().insert();
     UserDto user = db.users().insertUser();
@@ -72,9 +72,9 @@ public class OrganizationMemberDaoTest {
     db.organizations().addMember(organization, anotherUser);
     db.organizations().addMember(anotherOrganization, userInAnotherOrganization);
 
-    List<String> result = underTest.selectLoginsByOrganizationUuid(dbSession, organization.getUuid());
+    List<String> result = underTest.selectUserUuidsByOrganizationUuid(dbSession, organization.getUuid());
 
-    assertThat(result).containsOnly(user.getLogin(), anotherUser.getLogin());
+    assertThat(result).containsOnly(user.getUuid(), anotherUser.getUuid());
   }
 
   @Test
@@ -110,24 +110,24 @@ public class OrganizationMemberDaoTest {
   public void select_for_indexing() {
     OrganizationDto org1 = db.organizations().insert(o -> o.setUuid("ORG_1"));
     OrganizationDto org2 = db.organizations().insert(o -> o.setUuid("ORG_2"));
-    UserDto user1 = db.users().insertUser("L_1");
-    UserDto user2 = db.users().insertUser("L_2");
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
     db.organizations().addMember(org1, user1);
     db.organizations().addMember(org1, user2);
     db.organizations().addMember(org2, user1);
     List<Tuple> result = new ArrayList<>();
 
-    underTest.selectForUserIndexing(dbSession, Arrays.asList("L_1", "L_2"), (login, org) -> result.add(tuple(login, org)));
+    underTest.selectForUserIndexing(dbSession, asList(user1.getUuid(), user2.getUuid()), (login, org) -> result.add(tuple(login, org)));
 
-    assertThat(result).containsOnly(tuple("L_1", "ORG_1"), tuple("L_1", "ORG_2"), tuple("L_2", "ORG_1"));
+    assertThat(result).containsOnly(tuple(user1.getUuid(), "ORG_1"), tuple(user1.getUuid(), "ORG_2"), tuple(user2.getUuid(), "ORG_1"));
   }
 
   @Test
   public void select_all_for_indexing() {
     OrganizationDto org1 = db.organizations().insert(o -> o.setUuid("ORG_1"));
     OrganizationDto org2 = db.organizations().insert(o -> o.setUuid("ORG_2"));
-    UserDto user1 = db.users().insertUser("L_1");
-    UserDto user2 = db.users().insertUser("L_2");
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
     db.organizations().addMember(org1, user1);
     db.organizations().addMember(org1, user2);
     db.organizations().addMember(org2, user1);
@@ -135,7 +135,7 @@ public class OrganizationMemberDaoTest {
 
     underTest.selectAllForUserIndexing(dbSession, (login, org) -> result.add(tuple(login, org)));
 
-    assertThat(result).containsOnly(tuple("L_1", "ORG_1"), tuple("L_1", "ORG_2"), tuple("L_2", "ORG_1"));
+    assertThat(result).containsOnly(tuple(user1.getUuid(), "ORG_1"), tuple(user1.getUuid(), "ORG_2"), tuple(user2.getUuid(), "ORG_1"));
   }
 
   @Test
index f5ad3d6d2cae1997140b135950e3157e5daada9a..d9a59dbbe9e4fc92fd9f9765bc96c5cc1f71fcb2 100644 (file)
 package org.sonar.db.permission.template;
 
 import java.util.Collections;
-import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
-import org.sonar.api.web.UserRole;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
-import org.sonar.db.permission.PermissionQuery;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
 
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.web.UserRole.ADMIN;
+import static org.sonar.api.web.UserRole.CODEVIEWER;
+import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.db.permission.PermissionQuery.builder;
 
 public class UserWithPermissionTemplateDaoTest {
 
-  private static final Long TEMPLATE_ID = 50L;
-
   @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  public DbTester db = DbTester.create(System2.INSTANCE);
 
-  private DbSession dbSession = dbTester.getSession();
+  private DbSession dbSession = db.getSession();
 
-  private PermissionTemplateDao underTest = dbTester.getDbClient().permissionTemplateDao();
+  private PermissionTemplateDao underTest = db.getDbClient().permissionTemplateDao();
 
   @Test
   public void select_logins() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
-
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().build(), TEMPLATE_ID)).containsOnly("user1", "user2", "user3");
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().withAtLeastOnePermission().setPermission("user").build(),
-      TEMPLATE_ID)).containsOnly("user1", "user2");
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser();
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, ADMIN);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, CODEVIEWER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
+    PermissionTemplateDto anotherPermissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(anotherPermissionTemplate, user1, USER);
+
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).build(),
+      permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin(), user3.getLogin());
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).withAtLeastOnePermission().setPermission(USER).build(),
+      permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin());
   }
 
   @Test
   public void return_no_logins_on_unknown_template_key() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
-
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().setPermission("user").withAtLeastOnePermission().build(), 999L)).isEmpty();
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    db.organizations().addMember(organization, user);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user, USER);
+
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).setPermission(USER).withAtLeastOnePermission().build(), 999L))
+        .isEmpty();
   }
 
   @Test
   public void select_only_logins_with_permission() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
-
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(
-      dbSession,
-      newQuery().setPermission("user").withAtLeastOnePermission().build(),
-      TEMPLATE_ID)).containsOnly("user1", "user2");
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser();
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, ADMIN);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, CODEVIEWER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
+    PermissionTemplateDto anotherPermissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(anotherPermissionTemplate, user1, USER);
+
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).setPermission(USER).withAtLeastOnePermission().build(),
+      permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin());
   }
 
   @Test
   public void select_only_enable_users() {
-    dbTester.prepareDbUnit(getClass(), "select_only_enable_users.xml");
-
-    List<String> result = underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().setPermission("user").build(), TEMPLATE_ID);
-    assertThat(result).hasSize(2);
-
-    // Disabled user should not be returned
-    assertThat(result.stream().filter(input -> input.equals("disabledUser")).findFirst()).isEmpty();
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    UserDto disabledUser = db.users().insertUser(u -> u.setActive(false));
+    db.organizations().addMember(organization, user, disabledUser);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, disabledUser, USER);
+
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).setPermission(USER).build(), permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user.getLogin());
   }
 
   @Test
   public void search_by_user_name() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser(u -> u.setName("User1"));
+    UserDto user2 = db.users().insertUser(u -> u.setName("User2"));
+    UserDto user3 = db.users().insertUser(u -> u.setName("User3"));
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
 
-    List<String> result = underTest.selectUserLoginsByQueryAndTemplate(
-      dbSession, newQuery().withAtLeastOnePermission().setPermission("user").setSearchQuery("SEr1").build(),
-      TEMPLATE_ID);
-    assertThat(result).containsOnly("user1");
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(
+      dbSession, builder().setOrganizationUuid(organization.getUuid()).withAtLeastOnePermission().setPermission(USER).setSearchQuery("SEr1").build(),
+      permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user1.getLogin());
 
-    result = underTest.selectUserLoginsByQueryAndTemplate(
-      dbSession, newQuery().withAtLeastOnePermission().setPermission("user").setSearchQuery("user").build(),
-      TEMPLATE_ID);
-    assertThat(result).hasSize(2);
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(
+      dbSession, builder().setOrganizationUuid(organization.getUuid()).withAtLeastOnePermission().setPermission(USER).setSearchQuery("user").build(),
+      permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin());
   }
 
   @Test
   public void should_be_sorted_by_user_name() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions_should_be_sorted_by_user_name.xml");
-
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().build(), TEMPLATE_ID)).containsOnly("user1", "user2", "user3");
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser(u -> u.setName("User3"));
+    UserDto user2 = db.users().insertUser(u -> u.setName("User1"));
+    UserDto user3 = db.users().insertUser(u -> u.setName("User2"));
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
+
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).build(), permissionTemplate.getId()))
+        .containsExactly(user2.getLogin(), user3.getLogin(), user1.getLogin());
   }
 
   @Test
   public void should_be_paginated() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
-
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().setPageIndex(1).setPageSize(2).build(), TEMPLATE_ID)).containsOnly("user1", "user2");
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().setPageIndex(2).setPageSize(2).build(), TEMPLATE_ID)).containsOnly("user3");
-    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession, newQuery().setPageIndex(3).setPageSize(1).build(), TEMPLATE_ID)).containsOnly("user3");
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser(u -> u.setName("User1"));
+    UserDto user2 = db.users().insertUser(u -> u.setName("User2"));
+    UserDto user3 = db.users().insertUser(u -> u.setName("User3"));
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
+
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).setPageIndex(1).setPageSize(2).build(), permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin());
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).setPageIndex(2).setPageSize(2).build(), permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user3.getLogin());
+    assertThat(underTest.selectUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).setPageIndex(3).setPageSize(1).build(), permissionTemplate.getId()))
+        .containsExactlyInAnyOrder(user3.getLogin());
   }
 
   @Test
   public void count_users() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
-
-    assertThat(underTest.countUserLoginsByQueryAndTemplate(dbSession, newQuery().build(), TEMPLATE_ID)).isEqualTo(3);
-    assertThat(underTest.countUserLoginsByQueryAndTemplate(dbSession, newQuery().withAtLeastOnePermission().setPermission("user").build(), TEMPLATE_ID)).isEqualTo(2);
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser();
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
+
+    assertThat(underTest.countUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).build(), permissionTemplate.getId()))
+        .isEqualTo(3);
+    assertThat(underTest.countUserLoginsByQueryAndTemplate(dbSession,
+      builder().setOrganizationUuid(organization.getUuid()).withAtLeastOnePermission().setPermission("user").build(), permissionTemplate.getId()))
+        .isEqualTo(2);
   }
 
   @Test
   public void select_user_permission_templates_by_template_and_logins() {
-    dbTester.prepareDbUnit(getClass(), "users_with_permissions.xml");
-
-    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, 50L, singletonList("user1")))
+    OrganizationDto organization = db.organizations().insert();
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser();
+    db.organizations().addMember(organization, user1, user2, user3);
+    PermissionTemplateDto permissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, USER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, ADMIN);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user1, CODEVIEWER);
+    db.permissionTemplates().addUserToTemplate(permissionTemplate, user2, USER);
+    PermissionTemplateDto anotherPermissionTemplate = db.permissionTemplates().insertTemplate();
+    db.permissionTemplates().addUserToTemplate(anotherPermissionTemplate, user1, USER);
+
+    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, permissionTemplate.getId(), singletonList(user1.getLogin())))
       .extracting(PermissionTemplateUserDto::getUserLogin, PermissionTemplateUserDto::getPermission)
-      .containsOnly(
-        tuple("user1", UserRole.USER),
-        tuple("user1", UserRole.ADMIN),
-        tuple("user1", UserRole.CODEVIEWER)
-      );
+      .containsExactlyInAnyOrder(
+        tuple(user1.getLogin(), USER),
+        tuple(user1.getLogin(), ADMIN),
+        tuple(user1.getLogin(), CODEVIEWER));
 
-    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, 50L, asList("user1", "user2", "user3")))
+    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, permissionTemplate.getId(), asList(user1.getLogin(), user2.getLogin(), user2.getLogin())))
       .extracting(PermissionTemplateUserDto::getUserLogin, PermissionTemplateUserDto::getPermission)
-      .containsOnly(
-        tuple("user1", UserRole.USER),
-        tuple("user1", UserRole.ADMIN),
-        tuple("user1", UserRole.CODEVIEWER),
-        tuple("user2", UserRole.USER)
-      );
-
-    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, 50L, singletonList("unknown"))).isEmpty();
-    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, 50L, Collections.emptyList())).isEmpty();
-    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, 123L, singletonList("user1"))).isEmpty();
-  }
-
-  private PermissionQuery.Builder newQuery() {
-    return builder().setOrganizationUuid("ORG_UUID");
+      .containsExactlyInAnyOrder(
+        tuple(user1.getLogin(), USER),
+        tuple(user1.getLogin(), ADMIN),
+        tuple(user1.getLogin(), CODEVIEWER),
+        tuple(user2.getLogin(), USER));
+
+    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, permissionTemplate.getId(), singletonList("unknown"))).isEmpty();
+    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, permissionTemplate.getId(), Collections.emptyList())).isEmpty();
+    assertThat(underTest.selectUserPermissionsByTemplateIdAndUserLogins(dbSession, 123L, singletonList(user1.getLogin()))).isEmpty();
   }
 }
index a01c5d52058a8fc4adc7cdf5a378bd9b05db6b3e..cf016e343c9df5ecd66efb807c3348974a086c8a 100644 (file)
@@ -55,7 +55,6 @@ import static org.mockito.Mockito.when;
 import static org.sonar.db.property.PropertyTesting.newComponentPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newGlobalPropertyDto;
 import static org.sonar.db.property.PropertyTesting.newUserPropertyDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
 
 @RunWith(DataProviderRunner.class)
 public class PropertiesDaoTest {
@@ -72,28 +71,28 @@ public class PropertiesDaoTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
   @Rule
-  public DbTester dbTester = DbTester.create(system2);
+  public DbTester db = DbTester.create(system2);
 
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession session = dbTester.getSession();
+  private DbClient dbClient = db.getDbClient();
+  private DbSession session = db.getSession();
 
-  private PropertiesDao underTest = dbTester.getDbClient().propertiesDao();
+  private PropertiesDao underTest = db.getDbClient().propertiesDao();
 
   @Test
-  public void shouldFindUsersForNotification() throws SQLException {
+  public void shouldFindUsersForNotification() {
     ComponentDto project1 = insertPrivateProject("uuid_45");
     ComponentDto project2 = insertPrivateProject("uuid_56");
-    UserDto user1 = dbTester.users().insertUser(u -> u.setLogin("user1"));
-    UserDto user2 = dbTester.users().insertUser(u -> u.setLogin("user2"));
-    UserDto user3 = dbTester.users().insertUser(u -> u.setLogin("user3"));
+    UserDto user1 = db.users().insertUser(u -> u.setLogin("user1"));
+    UserDto user2 = db.users().insertUser(u -> u.setLogin("user2"));
+    UserDto user3 = db.users().insertUser(u -> u.setLogin("user3"));
     insertProperty("notification.NewViolations.Email", "true", project1.getId(), user2.getId());
     insertProperty("notification.NewViolations.Twitter", "true", null, user3.getId());
     insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), user1.getId());
     insertProperty("notification.NewViolations.Twitter", "true", project1.getId(), user2.getId());
     insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), user3.getId());
-    dbTester.users().insertProjectPermissionOnUser(user2, UserRole.USER, project1);
-    dbTester.users().insertProjectPermissionOnUser(user3, UserRole.USER, project2);
-    dbTester.users().insertProjectPermissionOnUser(user1, UserRole.USER, project2);
+    db.users().insertProjectPermissionOnUser(user2, UserRole.USER, project1);
+    db.users().insertProjectPermissionOnUser(user3, UserRole.USER, project2);
+    db.users().insertProjectPermissionOnUser(user1, UserRole.USER, project2);
 
     assertThat(underTest.findUsersForNotification("NewViolations", "Email", null))
       .isEmpty();
@@ -121,9 +120,9 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void hasNotificationSubscribers() throws SQLException {
-    int userId1 = dbTester.users().insertUser(u -> u.setLogin("user1")).getId();
-    int userId2 = dbTester.users().insertUser(u -> u.setLogin("user2")).getId();
+  public void hasNotificationSubscribers() {
+    int userId1 = db.users().insertUser(u -> u.setLogin("user1")).getId();
+    int userId2 = db.users().insertUser(u -> u.setLogin("user2")).getId();
     Long projectId = insertPrivateProject("PROJECT_A").getId();
     // global subscription
     insertProperty("notification.DispatcherWithGlobalSubscribers.Email", "true", null, userId2);
@@ -156,7 +155,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void selectGlobalProperties() throws SQLException {
+  public void selectGlobalProperties() {
     // global
     long id1 = insertProperty("global.one", "one", null, null);
     long id2 = insertProperty("global.two", "two", null, null);
@@ -180,7 +179,7 @@ public class PropertiesDaoTest {
 
   @Test
   @UseDataProvider("allValuesForSelect")
-  public void selectGlobalProperties_supports_all_values(String dbValue, String expected) throws SQLException {
+  public void selectGlobalProperties_supports_all_values(String dbValue, String expected) {
     insertProperty("global.one", dbValue, null, null);
 
     List<PropertyDto> dtos = underTest.selectGlobalProperties();
@@ -194,7 +193,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void selectGlobalProperty() throws SQLException {
+  public void selectGlobalProperty() {
     // global
     insertProperty("global.one", "one", null, null);
     insertProperty("global.two", "two", null, null);
@@ -215,7 +214,7 @@ public class PropertiesDaoTest {
 
   @Test
   @UseDataProvider("allValuesForSelect")
-  public void selectGlobalProperty_supports_all_values(String dbValue, String expected) throws SQLException {
+  public void selectGlobalProperty_supports_all_values(String dbValue, String expected) {
     insertProperty("global.one", dbValue, null, null);
 
     assertThatDto(underTest.selectGlobalProperty("global.one"))
@@ -225,7 +224,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void selectProjectProperties() throws SQLException {
+  public void selectProjectProperties() {
     ComponentDto projectDto = insertPrivateProject("A");
     long projectId = projectDto.getId();
     // global
@@ -250,7 +249,7 @@ public class PropertiesDaoTest {
 
   @Test
   @UseDataProvider("allValuesForSelect")
-  public void selectProjectProperties_supports_all_values(String dbValue, String expected) throws SQLException {
+  public void selectProjectProperties_supports_all_values(String dbValue, String expected) {
     ComponentDto projectDto = insertPrivateProject("A");
     insertProperty("project.one", dbValue, projectDto.getId(), null);
 
@@ -274,7 +273,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void selectProjectProperty() throws SQLException {
+  public void selectProjectProperty() {
     insertProperty("project.one", "one", 10L, null);
 
     PropertyDto property = underTest.selectProjectProperty(10L, "project.one");
@@ -288,34 +287,34 @@ public class PropertiesDaoTest {
 
   @Test
   public void selectEnabledDescendantModuleProperties() {
-    dbTester.prepareDbUnit(getClass(), "select_module_properties_tree.xml");
+    db.prepareDbUnit(getClass(), "select_module_properties_tree.xml");
 
-    List<PropertyDto> properties = underTest.selectEnabledDescendantModuleProperties("ABCD", dbTester.getSession());
+    List<PropertyDto> properties = underTest.selectEnabledDescendantModuleProperties("ABCD", db.getSession());
     assertThat(properties.size()).isEqualTo(4);
     assertThat(properties).extracting("key").containsOnly("struts.one", "core.one", "core.two", "data.one");
     assertThat(properties).extracting("value").containsOnly("one", "two");
 
-    properties = underTest.selectEnabledDescendantModuleProperties("EFGH", dbTester.getSession());
+    properties = underTest.selectEnabledDescendantModuleProperties("EFGH", db.getSession());
     assertThat(properties.size()).isEqualTo(3);
     assertThat(properties).extracting("key").containsOnly("core.one", "core.two", "data.one");
 
-    properties = underTest.selectEnabledDescendantModuleProperties("FGHI", dbTester.getSession());
+    properties = underTest.selectEnabledDescendantModuleProperties("FGHI", db.getSession());
     assertThat(properties.size()).isEqualTo(1);
     assertThat(properties).extracting("key").containsOnly("data.one");
 
-    assertThat(underTest.selectEnabledDescendantModuleProperties("unknown-result.xml", dbTester.getSession()).size()).isEqualTo(0);
+    assertThat(underTest.selectEnabledDescendantModuleProperties("unknown-result.xml", db.getSession()).size()).isEqualTo(0);
   }
 
   @Test
   @UseDataProvider("allValuesForSelect")
-  public void selectEnabledDescendantModuleProperties_supports_all_values(String dbValue, String expected) throws SQLException {
+  public void selectEnabledDescendantModuleProperties_supports_all_values(String dbValue, String expected) {
     String projectUuid = "A";
     ComponentDto project = ComponentTesting.newPrivateProjectDto(OrganizationTesting.newOrganizationDto(), projectUuid);
     dbClient.componentDao().insert(session, project);
     long projectId = project.getId();
     insertProperty("project.one", dbValue, projectId, null);
 
-    List<PropertyDto> dtos = underTest.selectEnabledDescendantModuleProperties(projectUuid, dbTester.getSession());
+    List<PropertyDto> dtos = underTest.selectEnabledDescendantModuleProperties(projectUuid, db.getSession());
     assertThat(dtos)
       .hasSize(1);
     assertThatDto(dtos.iterator().next())
@@ -326,7 +325,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void select_by_query() throws SQLException {
+  public void select_by_query() {
     // global
     insertProperty("global.one", "one", null, null);
     insertProperty("global.two", "two", null, null);
@@ -340,19 +339,19 @@ public class PropertiesDaoTest {
     // other
     insertProperty("other.one", "one", 12L, null);
 
-    List<PropertyDto> results = underTest.selectByQuery(PropertyQuery.builder().setKey("user.two").setComponentId(10L).setUserId(100).build(), dbTester.getSession());
+    List<PropertyDto> results = underTest.selectByQuery(PropertyQuery.builder().setKey("user.two").setComponentId(10L).setUserId(100).build(), db.getSession());
     assertThat(results).hasSize(1);
     assertThat(results.get(0).getValue()).isEqualTo("two");
 
-    results = underTest.selectByQuery(PropertyQuery.builder().setKey("user.one").setUserId(100).build(), dbTester.getSession());
+    results = underTest.selectByQuery(PropertyQuery.builder().setKey("user.one").setUserId(100).build(), db.getSession());
     assertThat(results).hasSize(1);
     assertThat(results.get(0).getValue()).isEqualTo("one");
   }
 
   @Test
-  public void select_global_properties_by_keys() throws Exception {
+  public void select_global_properties_by_keys() {
     insertPrivateProject("A");
-    int userId = dbTester.users().insertUser(u -> u.setLogin("B")).getId();
+    int userId = db.users().insertUser(u -> u.setLogin("B")).getId();
 
     String key = "key";
     String anotherKey = "anotherKey";
@@ -376,9 +375,9 @@ public class PropertiesDaoTest {
 
   @Test
   public void select_component_properties_by_ids() {
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto project2 = dbTester.components().insertPrivateProject();
-    UserDto user = dbTester.users().insertUser();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    UserDto user = db.users().insertUser();
 
     String key = "key";
     String anotherKey = "anotherKey";
@@ -402,9 +401,9 @@ public class PropertiesDaoTest {
 
   @Test
   public void select_properties_by_keys_and_component_ids() {
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto project2 = dbTester.components().insertPrivateProject();
-    UserDto user = dbTester.users().insertUser();
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    UserDto user = db.users().insertUser();
 
     String key = "key";
     String anotherKey = "anotherKey";
@@ -432,6 +431,25 @@ public class PropertiesDaoTest {
     assertThat(underTest.selectPropertiesByKeysAndComponentIds(session, newHashSet("unknown"), newHashSet(123456789L))).isEmpty();
   }
 
+  @Test
+  public void select_by_key_and_matching_value() {
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    db.properties().insertProperties(
+      newComponentPropertyDto("key", "value", project1),
+      newComponentPropertyDto("key", "value", project2),
+      newGlobalPropertyDto("key", "value"),
+      newComponentPropertyDto("another key", "value", project1));
+
+    assertThat(underTest.selectByKeyAndMatchingValue(db.getSession(), "key", "value"))
+    .extracting(PropertyDto::getValue, PropertyDto::getResourceId)
+    .containsExactlyInAnyOrder(
+      tuple("value", project1.getId()),
+      tuple("value", project2.getId()),
+      tuple("value", null)
+    );
+  }
+
   @Test
   public void saveProperty_inserts_global_properties_when_they_do_not_exist_in_db() {
     when(system2.now()).thenReturn(DATE_1, DATE_2, DATE_3, DATE_4, DATE_5);
@@ -654,7 +672,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void delete_project_property() throws SQLException {
+  public void delete_project_property() {
     long projectId1 = insertPrivateProject("A").getId();
     long projectId2 = insertPrivateProject("B").getId();
     long projectId3 = insertPrivateProject("C").getId();
@@ -703,7 +721,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void delete_project_properties() throws SQLException {
+  public void delete_project_properties() {
     long id1 = insertProperty("sonar.profile.java", "Sonar Way", 1L, null);
     long id2 = insertProperty("sonar.profile.java", "Sonar Way", 2L, null);
 
@@ -742,7 +760,7 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void deleteGlobalProperty() throws SQLException {
+  public void deleteGlobalProperty() {
     // global
     long id1 = insertProperty("global.key", "new_global", null, null);
     long id2 = insertProperty("to_be_deleted", "xxx", null, null);
@@ -775,13 +793,13 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void delete_by_organization_and_user() throws SQLException {
-    OrganizationDto organization = dbTester.organizations().insert();
-    OrganizationDto anotherOrganization = dbTester.organizations().insert();
-    ComponentDto project = dbTester.components().insertPrivateProject(organization);
-    ComponentDto anotherProject = dbTester.components().insertPrivateProject(anotherOrganization);
-    UserDto user = dbTester.users().insertUser();
-    UserDto anotherUser = dbTester.users().insertUser();
+  public void delete_by_organization_and_user() {
+    OrganizationDto organization = db.organizations().insert();
+    OrganizationDto anotherOrganization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
+    UserDto user = db.users().insertUser();
+    UserDto anotherUser = db.users().insertUser();
     insertProperty("KEY_11", "VALUE", project.getId(), user.getId());
     insertProperty("KEY_12", "VALUE", project.getId(), user.getId());
     insertProperty("KEY_11", "VALUE", project.getId(), anotherUser.getId());
@@ -797,13 +815,13 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void delete_by_organization_and_matching_login() throws SQLException {
-    OrganizationDto organization = dbTester.organizations().insert();
-    OrganizationDto anotherOrganization = dbTester.organizations().insert();
-    ComponentDto project = dbTester.components().insertPrivateProject(organization);
-    ComponentDto anotherProject = dbTester.components().insertPrivateProject(anotherOrganization);
-    UserDto user = dbTester.users().insertUser();
-    UserDto anotherUser = dbTester.users().insertUser();
+  public void delete_by_organization_and_matching_login() {
+    OrganizationDto organization = db.organizations().insert();
+    OrganizationDto anotherOrganization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
+    UserDto user = db.users().insertUser();
+    UserDto anotherUser = db.users().insertUser();
     insertProperty("KEY_11", user.getLogin(), project.getId(), null);
     insertProperty("KEY_12", user.getLogin(), project.getId(), null);
     insertProperty("KEY_11", anotherUser.getLogin(), project.getId(), null);
@@ -819,9 +837,9 @@ public class PropertiesDaoTest {
   }
 
   @Test
-  public void delete_by_key_and_value() throws SQLException {
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto anotherProject = dbTester.components().insertPrivateProject();
+  public void delete_by_key_and_value() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto anotherProject = db.components().insertPrivateProject();
     insertProperty("KEY", "VALUE", null, null);
     insertProperty("KEY", "VALUE", project.getId(), null);
     insertProperty("KEY", "VALUE", null, 100);
@@ -832,9 +850,9 @@ public class PropertiesDaoTest {
     insertProperty("ANOTHER_KEY", "VALUE", project.getId(), 100);
 
     underTest.deleteByKeyAndValue(session, "KEY", "VALUE");
-    dbTester.commit();
+    db.commit();
 
-    assertThat(dbTester.select("select prop_key as \"key\", text_value as \"value\", resource_id as \"projectId\", user_id as \"userId\" from properties"))
+    assertThat(db.select("select prop_key as \"key\", text_value as \"value\", resource_id as \"projectId\", user_id as \"userId\" from properties"))
       .extracting((row) -> row.get("key"), (row) -> row.get("value"), (row) -> row.get("projectId"), (row) -> row.get("userId"))
       .containsOnly(tuple("KEY", "ANOTHER_VALUE", null, null), tuple("ANOTHER_KEY", "VALUE", project.getId(), 100L));
   }
@@ -999,7 +1017,7 @@ public class PropertiesDaoTest {
     session.commit();
   }
 
-  private long insertProperty(String key, @Nullable String value, @Nullable Long resourceId, @Nullable Integer userId, long createdAt) throws SQLException {
+  private long insertProperty(String key, @Nullable String value, @Nullable Long resourceId, @Nullable Integer userId, long createdAt) {
     when(system2.now()).thenReturn(createdAt);
     return insertProperty(key, value, resourceId, userId);
   }
@@ -1009,20 +1027,16 @@ public class PropertiesDaoTest {
       .setResourceId(resourceId)
       .setUserId(userId)
       .setValue(value);
-    dbTester.properties().insertProperty(dto);
+    db.properties().insertProperty(dto);
 
-    return (long) dbTester.selectFirst(session, "select id as \"id\" from properties" +
+    return (long) db.selectFirst(session, "select id as \"id\" from properties" +
       " where prop_key='" + key + "'" +
       " and user_id" + (userId == null ? " is null" : "='" + userId + "'") +
       " and resource_id" + (resourceId == null ? " is null" : "='" + resourceId + "'")).get("id");
   }
 
   private ComponentDto insertPrivateProject(String uuid) {
-    String key = "project" + uuid;
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.getDefaultOrganization(), uuid).setDbKey(key);
-    dbClient.componentDao().insert(session, project);
-    dbTester.commit();
-    return project;
+    return db.components().insertPrivateProject(db.getDefaultOrganization(), uuid);
   }
 
   private static PropertyDtoAssert assertThatDto(@Nullable PropertyDto dto) {
@@ -1030,15 +1044,15 @@ public class PropertiesDaoTest {
   }
 
   private PropertiesRowAssert assertThatPropertiesRow(String key, @Nullable Integer userId, @Nullable Integer componentId) {
-    return new PropertiesRowAssert(dbTester, key, userId, componentId);
+    return new PropertiesRowAssert(db, key, userId, componentId);
   }
 
   private PropertiesRowAssert assertThatPropertiesRow(String key) {
-    return new PropertiesRowAssert(dbTester, key);
+    return new PropertiesRowAssert(db, key);
   }
 
   private PropertiesRowAssert assertThatPropertiesRow(long id) {
-    return new PropertiesRowAssert(dbTester, id);
+    return new PropertiesRowAssert(db, id);
   }
 
 }
index bed7f51695a19c02fe533eb9000fb146728de185..febc285cd918e9effe36596dd45a7faa89efe261 100644 (file)
@@ -34,8 +34,6 @@ import static org.assertj.core.data.MapEntry.entry;
 import static org.sonar.db.user.GroupMembershipQuery.IN;
 import static org.sonar.db.user.GroupMembershipQuery.OUT;
 import static org.sonar.db.user.GroupMembershipQuery.builder;
-import static org.sonar.db.user.UserTesting.newDisabledUser;
-import static org.sonar.db.user.UserTesting.newUserDto;
 
 public class GroupMembershipDaoTest {
 
@@ -55,9 +53,9 @@ public class GroupMembershipDaoTest {
   @Before
   public void setUp() throws Exception {
     organizationDto = db.organizations().insert();
-    user1 = db.users().insertUser(newUserDto("admin login", "Admin name", "admin@email.com"));
-    user2 = db.users().insertUser(newUserDto("not.admin", "Not Admin", "Not Admin"));
-    user3 = db.users().insertUser(newDisabledUser("inactive"));
+    user1 = db.users().insertUser(u -> u.setLogin("admin login").setName("Admin name").setEmail("admin@email.com"));
+    user2 = db.users().insertUser(u -> u.setLogin("not.admin").setName("Not Admin").setEmail("Not Admin"));
+    user3 = db.users().insertUser(u -> u.setLogin("inactive").setActive(false));
     group1 = db.users().insertGroup(organizationDto, "sonar-administrators");
     group2 = db.users().insertGroup(organizationDto, "sonar-users");
     group3 = db.users().insertGroup(organizationDto, "sonar-reviewers");
index 781451868252455325f42dc228f848825e323427..2d0f536eaa61946f409114414159f2980ba614e5 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.user;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
@@ -67,6 +68,16 @@ public class UserDaoTest {
     when(system2.now()).thenReturn(NOW);
   }
 
+  @Test
+  public void selectByUuid() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser(user -> user.setActive(false));
+
+    assertThat(underTest.selectByUuid(session, user1.getUuid())).isNotNull();
+    assertThat(underTest.selectByUuid(session, user2.getUuid())).isNotNull();
+    assertThat(underTest.selectByUuid(session, "unknown")).isNull();
+  }
+
   @Test
   public void selectUsersIds() {
     UserDto user1 = db.users().insertUser(user -> user.setLogin("user1"));
@@ -109,6 +120,17 @@ public class UserDaoTest {
     assertThat(users).extracting("login").containsExactlyInAnyOrder("user1", "inactive_user");
   }
 
+  @Test
+  public void selectUsersByUuids() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
+    UserDto user3 = db.users().insertUser(user -> user.setActive(false));
+
+    assertThat((Collection<UserDto>) underTest.selectByUuids(session, asList(user1.getUuid(), user2.getUuid(), user3.getUuid()))).hasSize(3);
+    assertThat((Collection<UserDto>) underTest.selectByUuids(session, asList(user1.getUuid(), "unknown"))).hasSize(1);
+    assertThat((Collection<UserDto>) underTest.selectByUuids(session, Collections.emptyList())).isEmpty();
+  }
+
   @Test
   public void selectUsersByLogins_empty_logins() {
     // no need to access db
@@ -320,8 +342,9 @@ public class UserDaoTest {
       .setSalt("1234")
       .setCryptedPassword("abcd")
       .setHashMethod("SHA1")
-      .setExternalIdentity("johngithub")
+      .setExternalLogin("johngithub")
       .setExternalIdentityProvider("github")
+      .setExternalId("EXT_ID")
       .setLocal(true)
       .setCreatedAt(date)
       .setUpdatedAt(date)
@@ -342,8 +365,9 @@ public class UserDaoTest {
     assertThat(user.getSalt()).isEqualTo("1234");
     assertThat(user.getCryptedPassword()).isEqualTo("abcd");
     assertThat(user.getHashMethod()).isEqualTo("SHA1");
-    assertThat(user.getExternalIdentity()).isEqualTo("johngithub");
+    assertThat(user.getExternalLogin()).isEqualTo("johngithub");
     assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
+    assertThat(user.getExternalId()).isEqualTo("EXT_ID");
     assertThat(user.isLocal()).isTrue();
     assertThat(user.isRoot()).isFalse();
     assertThat(user.getHomepageType()).isEqualTo("project");
@@ -360,9 +384,9 @@ public class UserDaoTest {
       .setLocal(true)
       .setOnboarded(false));
 
-    UserDto userUpdate = newUserDto()
-      .setId(1)
-      .setLogin("john")
+    underTest.update(db.getSession(), newUserDto()
+      .setUuid(user.getUuid())
+      .setLogin("johnDoo")
       .setName("John Doo")
       .setEmail("jodoo@hn.com")
       .setScmAccounts(",jo.hn,john2,johndoo,")
@@ -371,17 +395,17 @@ public class UserDaoTest {
       .setSalt("12345")
       .setCryptedPassword("abcde")
       .setHashMethod("BCRYPT")
-      .setExternalIdentity("johngithub")
+      .setExternalLogin("johngithub")
       .setExternalIdentityProvider("github")
+      .setExternalId("EXT_ID")
       .setLocal(false)
       .setHomepageType("project")
-      .setHomepageParameter("OB1");
-    underTest.update(db.getSession(), userUpdate);
+      .setHomepageParameter("OB1"));
 
-    UserDto reloaded = underTest.selectByLogin(db.getSession(), user.getLogin());
+    UserDto reloaded = underTest.selectByUuid(db.getSession(), user.getUuid());
     assertThat(reloaded).isNotNull();
     assertThat(reloaded.getId()).isEqualTo(user.getId());
-    assertThat(reloaded.getLogin()).isEqualTo(user.getLogin());
+    assertThat(reloaded.getLogin()).isEqualTo("johnDoo");
     assertThat(reloaded.getName()).isEqualTo("John Doo");
     assertThat(reloaded.getEmail()).isEqualTo("jodoo@hn.com");
     assertThat(reloaded.isActive()).isFalse();
@@ -390,8 +414,9 @@ public class UserDaoTest {
     assertThat(reloaded.getSalt()).isEqualTo("12345");
     assertThat(reloaded.getCryptedPassword()).isEqualTo("abcde");
     assertThat(reloaded.getHashMethod()).isEqualTo("BCRYPT");
-    assertThat(reloaded.getExternalIdentity()).isEqualTo("johngithub");
+    assertThat(reloaded.getExternalLogin()).isEqualTo("johngithub");
     assertThat(reloaded.getExternalIdentityProvider()).isEqualTo("github");
+    assertThat(reloaded.getExternalId()).isEqualTo("EXT_ID");
     assertThat(reloaded.isLocal()).isFalse();
     assertThat(reloaded.isRoot()).isFalse();
     assertThat(reloaded.getHomepageType()).isEqualTo("project");
@@ -409,12 +434,14 @@ public class UserDaoTest {
 
     UserDto userReloaded = underTest.selectUserById(session, user.getId());
     assertThat(userReloaded.isActive()).isFalse();
+    assertThat(userReloaded.getLogin()).isNotNull();
+    assertThat(userReloaded.getExternalId()).isNotNull();
+    assertThat(userReloaded.getExternalLogin()).isNotNull();
+    assertThat(userReloaded.getExternalIdentityProvider()).isNotNull();
     assertThat(userReloaded.getEmail()).isNull();
     assertThat(userReloaded.getScmAccounts()).isNull();
     assertThat(userReloaded.getSalt()).isNull();
     assertThat(userReloaded.getCryptedPassword()).isNull();
-    assertThat(userReloaded.getExternalIdentity()).isNull();
-    assertThat(userReloaded.getExternalIdentityProvider()).isNull();
     assertThat(userReloaded.isRoot()).isFalse();
     assertThat(userReloaded.getUpdatedAt()).isEqualTo(NOW);
     assertThat(userReloaded.getHomepageType()).isNull();
@@ -576,6 +603,16 @@ public class UserDaoTest {
     assertThat(underTest.selectByEmail(session, "unknown")).isNull();
   }
 
+  @Test
+  public void select_by_external_id_and_identity_provider() {
+    UserDto activeUser = db.users().insertUser();
+    UserDto disableUser = db.users().insertUser(u -> u.setActive(false));
+
+    assertThat(underTest.selectByExternalIdAndIdentityProvider(session, activeUser.getExternalId(), activeUser.getExternalIdentityProvider())).isNotNull();
+    assertThat(underTest.selectByExternalIdAndIdentityProvider(session, disableUser.getExternalId(), disableUser.getExternalIdentityProvider())).isNotNull();
+    assertThat(underTest.selectByExternalIdAndIdentityProvider(session, "unknown", "unknown")).isNull();
+  }
+
   @Test
   public void setRoot_does_not_fail_on_non_existing_login() {
     underTest.setRoot(session, "unkown", true);
@@ -640,31 +677,31 @@ public class UserDaoTest {
   }
 
   @Test
-  public void scrollByLogins() {
+  public void scrollByLUuids() {
     UserDto u1 = insertUser(true);
     UserDto u2 = insertUser(false);
     UserDto u3 = insertUser(false);
 
     List<UserDto> result = new ArrayList<>();
-    underTest.scrollByLogins(db.getSession(), asList(u2.getLogin(), u3.getLogin(), "does not exist"), result::add);
+    underTest.scrollByUuids(db.getSession(), asList(u2.getUuid(), u3.getUuid(), "does not exist"), result::add);
 
-    assertThat(result).extracting(UserDto::getLogin, UserDto::getName)
-      .containsExactlyInAnyOrder(tuple(u2.getLogin(), u2.getName()), tuple(u3.getLogin(), u3.getName()));
+    assertThat(result).extracting(UserDto::getUuid, UserDto::getName)
+      .containsExactlyInAnyOrder(tuple(u2.getUuid(), u2.getName()), tuple(u3.getUuid(), u3.getName()));
   }
 
   @Test
-  public void scrollByLogins_scrolls_by_pages_of_1000_logins() {
-    List<String> logins = new ArrayList<>();
+  public void scrollByUuids_scrolls_by_pages_of_1000_uuids() {
+    List<String> uuids = new ArrayList<>();
     for (int i = 0; i < DatabaseUtils.PARTITION_SIZE_FOR_ORACLE + 10; i++) {
-      logins.add(insertUser(true).getLogin());
+      uuids.add(insertUser(true).getUuid());
     }
 
     List<UserDto> result = new ArrayList<>();
-    underTest.scrollByLogins(db.getSession(), logins, result::add);
+    underTest.scrollByUuids(db.getSession(), uuids, result::add);
 
     assertThat(result)
-      .extracting(UserDto::getLogin)
-      .containsExactlyInAnyOrder(logins.toArray(new String[0]));
+      .extracting(UserDto::getUuid)
+      .containsExactlyInAnyOrder(uuids.toArray(new String[0]));
   }
 
   @Test
index 897599f85f13b046c11b654bee609e1febb2ee97..69343f0b9cc30ee7f0fbc9fd98c28675ab4d848a 100644 (file)
@@ -37,8 +37,10 @@ import org.sonar.db.permission.UserPermissionDto;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
+import static java.util.Arrays.stream;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
 import static org.sonar.db.user.GroupTesting.newGroupDto;
+import static org.sonar.db.user.UserTesting.newDisabledUser;
 import static org.sonar.db.user.UserTesting.newUserDto;
 
 public class UserDbTester {
@@ -61,9 +63,17 @@ public class UserDbTester {
     return insertUser(dto);
   }
 
-  public UserDto insertUser(Consumer<UserDto> populateUserDto) {
+  @SafeVarargs
+  public final UserDto insertUser(Consumer<UserDto>... populators) {
     UserDto dto = newUserDto().setActive(true);
-    populateUserDto.accept(dto);
+    stream(populators).forEach(p -> p.accept(dto));
+    return insertUser(dto);
+  }
+
+  @SafeVarargs
+  public final UserDto insertDisabledUser(Consumer<UserDto>... populators) {
+    UserDto dto = newDisabledUser();
+    stream(populators).forEach(p -> p.accept(dto));
     return insertUser(dto);
   }
 
index 59a652939bd3333d99a2ab6e9224265360a05c15..debf135c232f68382dde9986b22211c23b9a81f3 100644 (file)
@@ -39,7 +39,8 @@ public class UserTesting {
       .setEmail(randomAlphanumeric(30))
       .setOnboarded(nextBoolean())
       .setScmAccounts(singletonList(randomAlphanumeric(40)))
-      .setExternalIdentity(randomAlphanumeric(40))
+      .setExternalId(randomAlphanumeric(40))
+      .setExternalLogin(randomAlphanumeric(40))
       .setExternalIdentityProvider(randomAlphanumeric(40))
       .setSalt(randomAlphanumeric(40))
       .setCryptedPassword(randomAlphanumeric(40))
@@ -60,7 +61,8 @@ public class UserTesting {
       .setName(name)
       .setEmail(email)
       .setLogin(login)
-      .setExternalIdentity(login)
+      .setExternalId(login)
+      .setExternalLogin(login)
       .setExternalIdentityProvider("sonarqube");
   }
 
@@ -70,18 +72,16 @@ public class UserTesting {
       .setName(name)
       .setEmail(email)
       .setLogin(login)
-      .setExternalIdentity(randomAlphanumeric(40))
+      .setExternalId(randomAlphanumeric(40))
+      .setExternalLogin(randomAlphanumeric(40))
       .setExternalIdentityProvider(randomAlphanumeric(40));
   }
 
-  public static UserDto newDisabledUser(String login) {
+  public static UserDto newDisabledUser() {
     return newUserDto()
-      .setLogin(login)
       .setActive(false)
       // All these fields are reset when disabling a user
       .setScmAccounts((String) null)
-      .setExternalIdentity(null)
-      .setExternalIdentityProvider(null)
       .setEmail(null)
       .setCryptedPassword(null)
       .setSalt(null);
diff --git a/server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml b/server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml
deleted file mode 100644 (file)
index 159c76b..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-<dataset>
-
-  <users id="200"
-         uuid="user1uuid"
-         login="user1"
-         name="User1"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="201"
-         uuid="user2uuid"
-         login="user2"
-         name="User2"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="202"
-         uuid="user3uuid"
-         login="user3"
-         name="User3"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="999"
-         uuid="user999uuid"
-         login="disabledUser"
-         name="disabledUser"
-         active="[false]"
-         is_root="[false]"
-         onboarded="[true]"/>
-
-  <organization_members
-      user_id="200"
-      organization_uuid="ORG_UUID"
-  />
-
-  <organization_members
-      user_id="201"
-      organization_uuid="ORG_UUID"
-  />
-
-  <organization_members
-      user_id="202"
-      organization_uuid="ORG_UUID"
-  />
-
-  <perm_templates_users id="1"
-                        user_id="200"
-                        permission_reference="user"
-                        template_id="50"/>
-  <perm_templates_users id="2"
-                        user_id="200"
-                        permission_reference="admin"
-                        template_id="50"/>
-  <perm_templates_users id="3"
-                        user_id="200"
-                        permission_reference="codeviewer"
-                        template_id="50"/>
-
-  <perm_templates_users id="4"
-                        user_id="201"
-                        permission_reference="user"
-                        template_id="50"/>
-
-</dataset>
diff --git a/server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions.xml b/server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions.xml
deleted file mode 100644 (file)
index 23599c6..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<dataset>
-
-  <users id="200"
-         uuid="user1uuid"
-         login="user1"
-         name="User1"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="201"
-         uuid="user2uuid"
-         login="user2"
-         name="User2"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="202"
-         uuid="user3uuid"
-         login="user3"
-         name="User3"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-
-  <organization_members
-    user_id="200"
-    organization_uuid="ORG_UUID"
-  />
-
-  <organization_members
-    user_id="201"
-    organization_uuid="ORG_UUID"
-  />
-
-  <organization_members
-    user_id="202"
-    organization_uuid="ORG_UUID"
-  />
-
-  <perm_templates_users id="1"
-                        user_id="200"
-                        permission_reference="user"
-                        template_id="50"/>
-  <perm_templates_users id="2"
-                        user_id="200"
-                        permission_reference="admin"
-                        template_id="50"/>
-  <perm_templates_users id="3"
-                        user_id="200"
-                        permission_reference="codeviewer"
-                        template_id="50"/>
-  <perm_templates_users id="4"
-                        user_id="200"
-                        permission_reference="user"
-                        template_id="51"/>
-
-  <perm_templates_users id="5"
-                        user_id="201"
-                        permission_reference="user"
-                        template_id="50"/>
-
-</dataset>
diff --git a/server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml b/server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml
deleted file mode 100644 (file)
index d8f866c..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<dataset>
-
-  <users id="200"
-         uuid="user3uuid"
-         login="user3"
-         name="User3"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="201"
-         uuid="user1uuid"
-         login="user1"
-         name="User1"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-  <users id="202"
-         uuid="user2uuid"
-         login="user2"
-         name="User2"
-         active="[true]"
-         is_root="[false]"
-         onboarded="[true]"/>
-
-  <organization_members
-      user_id="200"
-      organization_uuid="ORG_UUID"
-  />
-
-  <organization_members
-      user_id="201"
-      organization_uuid="ORG_UUID"
-  />
-
-  <organization_members
-      user_id="202"
-      organization_uuid="ORG_UUID"
-  />
-
-  <perm_templates_users id="1"
-                        user_id="200"
-                        permission_reference="user"
-                        template_id="50"/>
-  <perm_templates_users id="2"
-                        user_id="200"
-                        permission_reference="admin"
-                        template_id="50"/>
-  <perm_templates_users id="3"
-                        user_id="200"
-                        permission_reference="codeviewer"
-                        template_id="50"/>
-
-  <perm_templates_users id="4"
-                        user_id="201"
-                        permission_reference="user"
-                        template_id="50"/>
-
-</dataset>
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsers.java
new file mode 100644 (file)
index 0000000..9f2a45a
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddExternalIdToUsers extends DdlChange {
+
+  public AddExternalIdToUsers(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDialect(), "users")
+      .addColumn(newVarcharColumnDefBuilder()
+        .setColumnName("external_id")
+        .setLimit(255)
+        .setIsNullable(true)
+        .build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsers.java
new file mode 100644 (file)
index 0000000..41dfef1
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddUniqueIndexesOnUsers extends DdlChange {
+
+  public AddUniqueIndexesOnUsers(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable("users")
+      .setName("users_uuid")
+      .setUnique(true)
+      .addColumn(notNullableColumn("uuid", 40))
+      .build());
+
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable("users")
+      .setName("uniq_external_id")
+      .setUnique(true)
+      .addColumn(notNullableColumn("external_identity_provider", 100))
+      .addColumn(notNullableColumn("external_id", 255))
+      .build());
+  }
+
+  private static VarcharColumnDef notNullableColumn(String columnName, int limit) {
+    return newVarcharColumnDefBuilder()
+      .setColumnName(columnName)
+      .setLimit(limit)
+      .setIsNullable(false)
+      .build();
+  }
+
+}
index dae4cf27704df33030d368e3b62729b7de7d99bb..997d0407fcf5761a092b6e51e4c040478dd9ffd5 100644 (file)
@@ -36,6 +36,13 @@ public class DbVersion72 implements DbVersion {
       .add(2106, "Create PROJECT_MAPPINGS table", CreateProjectMappingsTable.class)
       .add(2107, "Add UUID on table USERS", AddUUIDtoUsers.class)
       .add(2108, "Populate USERS.UUID with USERS.LOGIN", PopulateUUIDOnUsers.class)
+      .add(2109, "Add EXTERNAL_ID on table users", AddExternalIdToUsers.class)
+      .add(2110, "Rename EXTERNAL_IDENTITY to EXTERNAL_LOGIN on table users", RenameExternalIdentityToExternalLoginOnUsers.class)
+      .add(2111, "Update null values from external columns and login of users", UpdateNullValuesFromExternalColumnsAndLoginOfUsers.class)
+      .add(2112, "Populate EXTERNAL_ID on table users", PopulateExternalIdOnUsers.class)
+      .add(2113, "Makes same columns of table users not nullable", MakeSomeColumnsOfUsersNotNullable.class)
+      .add(2114, "Add unique indexes on table users", AddUniqueIndexesOnUsers.class)
+
     ;
   }
 }
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullable.java
new file mode 100644 (file)
index 0000000..82f18e8
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.sql.DropIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class MakeSomeColumnsOfUsersNotNullable extends DdlChange {
+
+  public static final String USERS_TABLE = "users";
+  public static final String USERS_LOGIN_INDEX = "users_login";
+
+  public MakeSomeColumnsOfUsersNotNullable(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new DropIndexBuilder(getDialect())
+      .setTable(USERS_TABLE)
+      .setName(USERS_LOGIN_INDEX)
+      .build());
+
+    context.execute(new AlterColumnsBuilder(getDialect(), USERS_TABLE)
+      .updateColumn(notNullableColumn("uuid", 40))
+      .updateColumn(notNullableColumn("login", 255))
+      .updateColumn(notNullableColumn("external_id", 255))
+      .updateColumn(notNullableColumn("external_login", 255))
+      .updateColumn(notNullableColumn("external_identity_provider", 100))
+      .build());
+
+    context.execute(new CreateIndexBuilder(getDialect())
+      .setTable(USERS_TABLE)
+      .setName(USERS_LOGIN_INDEX)
+      .addColumn(notNullableColumn("login", 255))
+      .setUnique(true)
+      .build());
+  }
+
+  private static VarcharColumnDef notNullableColumn(String columnName, int limit) {
+    return newVarcharColumnDefBuilder()
+      .setColumnName(columnName)
+      .setLimit(limit)
+      .setIsNullable(false)
+      .build();
+  }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsers.java
new file mode 100644 (file)
index 0000000..912c7a6
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class PopulateExternalIdOnUsers extends DataChange {
+
+  private final System2 system2;
+
+  public PopulateExternalIdOnUsers(Database db, System2 system2) {
+    super(db);
+    this.system2 = system2;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("users");
+    massUpdate.select("SELECT id, external_login FROM users WHERE external_id IS NULL");
+    massUpdate.update("UPDATE users SET external_id=?, updated_at=? WHERE id=?");
+
+    long now = system2.now();
+    massUpdate.execute((row, update) -> {
+      long id = row.getLong(1);
+      String externalLogin = row.getString(2);
+      update.setString(1, externalLogin);
+      update.setLong(2, now);
+      update.setLong(3, id);
+      return true;
+    });
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsers.java
new file mode 100644 (file)
index 0000000..da64eee
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.sql.RenameColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class RenameExternalIdentityToExternalLoginOnUsers extends DdlChange {
+
+  public RenameExternalIdentityToExternalLoginOnUsers(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new RenameColumnsBuilder(getDialect(), "users")
+      .renameColumn("external_identity",
+        newVarcharColumnDefBuilder()
+          .setColumnName("external_login")
+          .setLimit(255)
+          .build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsers.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsers.java
new file mode 100644 (file)
index 0000000..e18f678
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+/**
+ * The goal of this migration is to sanitize disabled USERS, regarding new way of authentication users.
+ * Indeed, authentication will search for user but LOGIN but also buy using EXTERNAL_ID and EXTERNAL_PROVIDER.
+ *
+ * As a consequence, these columns must be set as NOT NULL in order to add a UNIQUE index on them.
+ *
+ * Unfortunately, these columns were previously set as null when disabling a user, that's why we need to populate them.
+ */
+public class UpdateNullValuesFromExternalColumnsAndLoginOfUsers extends DataChange {
+
+  private static final Logger LOG = Loggers.get(UpdateNullValuesFromExternalColumnsAndLoginOfUsers.class);
+
+  private final System2 system2;
+  private UuidFactory uuidFactory;
+
+  public UpdateNullValuesFromExternalColumnsAndLoginOfUsers(Database db, System2 system2, UuidFactory uuidFactory) {
+    super(db);
+    this.system2 = system2;
+    this.uuidFactory = uuidFactory;
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate().rowPluralName("users");
+    massUpdate.select("SELECT id, login FROM users WHERE login IS NULL OR external_login IS NULL OR external_identity_provider IS NULL");
+    massUpdate.update("UPDATE users SET login=?, external_login=?, external_identity_provider=?, updated_at=? WHERE id=?");
+
+    long now = system2.now();
+    massUpdate.execute((row, update) -> {
+      long id = row.getLong(1);
+      String login = row.getString(2);
+      if (login == null) {
+        LOG.warn("No login has been found for user id '{}'. A UUID has been generated to not have null value.", id);
+        login = uuidFactory.create();
+      }
+      update.setString(1, login);
+      update.setString(2, login);
+      update.setString(3, "sonarqube");
+      update.setLong(4, now);
+      update.setLong(5, id);
+      return true;
+    });
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest.java
new file mode 100644 (file)
index 0000000..d967c3f
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class AddExternalIdToUsersTest {
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddExternalIdToUsersTest.class, "users.sql");
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddExternalIdToUsers underTest = new AddExternalIdToUsers(db.database());
+
+  @Test
+  public void column_is_added_to_table() throws SQLException {
+    underTest.execute();
+
+    db.assertColumnDefinition("users", "external_id", VARCHAR, 255, true);
+  }
+
+  @Test
+  public void migration_is_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+
+    underTest.execute();
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest.java
new file mode 100644 (file)
index 0000000..488636b
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+public class AddUniqueIndexesOnUsersTest {
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(AddUniqueIndexesOnUsersTest.class, "users.sql");
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private AddUniqueIndexesOnUsers underTest = new AddUniqueIndexesOnUsers(db.database());
+
+  @Test
+  public void add_unique_indexes() throws SQLException {
+    underTest.execute();
+
+    db.assertUniqueIndex("users", "users_uuid", "uuid");
+    db.assertUniqueIndex("users", "uniq_external_id", "external_identity_provider", "external_id");
+  }
+
+  @Test
+  public void migration_is_not_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+
+    underTest.execute();
+  }
+
+}
index fa799924abaf8f5f67ae241aa897d4e8d24fe424..1d4ab0135d7a725e3cc36445b2ccbaf0cd801394 100644 (file)
@@ -34,7 +34,7 @@ public class DbVersion72Test {
 
   @Test
   public void verify_migration_count() {
-    verifyMigrationCount(underTest, 9);
+    verifyMigrationCount(underTest, 15);
   }
 
 }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest.java
new file mode 100644 (file)
index 0000000..73c6016
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class MakeSomeColumnsOfUsersNotNullableTest {
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(MakeSomeColumnsOfUsersNotNullableTest.class, "users.sql");
+
+  private MakeSomeColumnsOfUsersNotNullable underTest = new MakeSomeColumnsOfUsersNotNullable(db.database());
+
+  @Test
+  public void columns_are_set_as_not_nullable() throws SQLException {
+    underTest.execute();
+
+    db.assertColumnDefinition("users", "uuid", VARCHAR, 40, false);
+    db.assertColumnDefinition("users", "login", VARCHAR, 255, false);
+    db.assertColumnDefinition("users", "external_id", VARCHAR, 255, false);
+    db.assertColumnDefinition("users", "external_login", VARCHAR, 255, false);
+    db.assertColumnDefinition("users", "external_identity_provider", VARCHAR, 100, false);
+    db.assertUniqueIndex("users", "users_login", "login");
+  }
+
+  @Test
+  public void migration_is_reentrant() throws SQLException {
+    underTest.execute();
+    underTest.execute();
+
+    db.assertColumnDefinition("users", "uuid", VARCHAR, 40, false);
+    db.assertColumnDefinition("users", "login", VARCHAR, 255, false);
+    db.assertColumnDefinition("users", "external_id", VARCHAR, 255, false);
+    db.assertColumnDefinition("users", "external_login", VARCHAR, 255, false);
+    db.assertColumnDefinition("users", "external_identity_provider", VARCHAR, 100, false);
+    db.assertUniqueIndex("users", "users_login", "login");
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest.java
new file mode 100644 (file)
index 0000000..9b77fd1
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.assertj.core.groups.Tuple;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class PopulateExternalIdOnUsersTest {
+
+  private static final long PAST = 5_000_000_000L;
+  private static final long NOW = 10_000_000_000L;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(PopulateExternalIdOnUsersTest.class, "users.sql");
+
+  private System2 system2 = new TestSystem2().setNow(NOW);
+
+  private PopulateExternalIdOnUsers underTest = new PopulateExternalIdOnUsers(db.database(), system2);
+
+  @Test
+  public void update_users() throws SQLException {
+    insertUser("USER_1", "user1", null);
+    insertUser("USER_2", "user2", "1234");
+
+    underTest.execute();
+
+    assertUsers(
+      tuple("USER_1", "user1", NOW),
+      tuple("USER_2", "1234", PAST));
+  }
+
+  @Test
+  public void migration_is_reentrant() throws SQLException {
+    insertUser("USER_1", "user1", null);
+    insertUser("USER_2", "user2", "1234");
+
+    underTest.execute();
+    underTest.execute();
+
+    assertUsers(
+      tuple("USER_1", "user1", NOW),
+      tuple("USER_2", "1234", PAST));
+  }
+
+  private void assertUsers(Tuple... expectedTuples) {
+    assertThat(db.select("SELECT LOGIN, EXTERNAL_ID, UPDATED_AT FROM USERS")
+      .stream()
+      .map(map -> new Tuple(map.get("LOGIN"), map.get("EXTERNAL_ID"), map.get("UPDATED_AT")))
+      .collect(Collectors.toList()))
+        .containsExactlyInAnyOrder(expectedTuples);
+  }
+
+  private void insertUser(String login, String externalLogin, @Nullable String externalId) {
+    db.executeInsert("USERS",
+      "LOGIN", login,
+      "EXTERNAL_ID", externalId,
+      "EXTERNAL_LOGIN", externalLogin,
+      "CREATED_AT", PAST,
+      "UPDATED_AT", PAST,
+      "IS_ROOT", false,
+      "ONBOARDED", false);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest.java
new file mode 100644 (file)
index 0000000..ff1f7df
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.CoreDbTester;
+
+import static java.sql.Types.VARCHAR;
+
+public class RenameExternalIdentityToExternalLoginOnUsersTest {
+
+  @Rule
+  public final CoreDbTester db = CoreDbTester.createForSchema(RenameExternalIdentityToExternalLoginOnUsersTest.class, "users.sql");
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private RenameExternalIdentityToExternalLoginOnUsers underTest = new RenameExternalIdentityToExternalLoginOnUsers(db.database());
+
+  @Test
+  public void column_is_renamed() throws SQLException {
+    underTest.execute();
+
+    db.assertColumnDefinition("users", "external_login", VARCHAR, 255, true);
+    db.assertColumnDoesNotExist("users", "external_identity");
+  }
+
+  @Test
+  public void migration_is_not_reentrant() throws SQLException {
+    underTest.execute();
+
+    expectedException.expect(IllegalStateException.class);
+
+    underTest.execute();
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest.java
new file mode 100644 (file)
index 0000000..596ce5c
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.platform.db.migration.version.v72;
+
+import java.sql.SQLException;
+import javax.annotation.Nullable;
+import org.assertj.core.groups.Tuple;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.CoreDbTester;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest {
+
+  private static final long PAST = 5_000_000_000L;
+  private static final long NOW = 10_000_000_000L;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest.class, "users.sql");
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private System2 system2 = new TestSystem2().setNow(NOW);
+
+  private UpdateNullValuesFromExternalColumnsAndLoginOfUsers underTest = new UpdateNullValuesFromExternalColumnsAndLoginOfUsers(db.database(), system2, new SequenceUuidFactory());
+
+  @Test
+  public void update_users() throws SQLException {
+    insertUser("USER_1", "user1", "github");
+    insertUser("USER_2", null, null);
+    insertUser("USER_3", "user", null);
+    insertUser("USER_4", null, "github");
+    insertUser(null, "user", "bitbucket");
+    insertUser(null, null, null);
+
+    underTest.execute();
+
+    assertUsers(
+      tuple("USER_1", "user1", "github", PAST),
+      tuple("USER_2", "USER_2", "sonarqube", NOW),
+      tuple("USER_3", "USER_3", "sonarqube", NOW),
+      tuple("USER_4", "USER_4", "sonarqube", NOW),
+      tuple("1", "1", "sonarqube", NOW),
+      tuple("2", "2", "sonarqube", NOW));
+  }
+
+  @Test
+  public void log_warning_when_login_is_null() throws SQLException {
+    insertUser(null, "user", "bitbucket");
+    long id = (long) db.selectFirst("SELECT ID FROM USERS").get("ID");
+
+    underTest.execute();
+
+    assertThat(logTester.logs(LoggerLevel.WARN))
+      .containsExactlyInAnyOrder(format("No login has been found for user id '%s'. A UUID has been generated to not have null value.", id));
+  }
+
+  @Test
+  public void is_reentrant() throws SQLException {
+    insertUser("USER_1", null, null);
+
+    underTest.execute();
+    underTest.execute();
+
+    assertUsers(tuple("USER_1", "USER_1", "sonarqube", NOW));
+  }
+
+  private void assertUsers(Tuple... expectedTuples) {
+    assertThat(db.select("SELECT LOGIN, EXTERNAL_LOGIN, EXTERNAL_IDENTITY_PROVIDER, UPDATED_AT FROM USERS")
+      .stream()
+      .map(map -> new Tuple(map.get("LOGIN"), map.get("EXTERNAL_LOGIN"), map.get("EXTERNAL_IDENTITY_PROVIDER"), map.get("UPDATED_AT")))
+      .collect(toList()))
+        .containsExactlyInAnyOrder(expectedTuples);
+  }
+
+  private void insertUser(@Nullable String login, @Nullable String externalLogin, @Nullable String externalIdentityProvider) {
+    db.executeInsert("USERS",
+      "LOGIN", login,
+      "EXTERNAL_LOGIN", externalLogin,
+      "EXTERNAL_IDENTITY_PROVIDER", externalIdentityProvider,
+      "CREATED_AT", PAST,
+      "UPDATED_AT", PAST,
+      "IS_ROOT", false,
+      "ONBOARDED", false);
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest/users.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest/users.sql
new file mode 100644 (file)
index 0000000..c1d3972
--- /dev/null
@@ -0,0 +1,22 @@
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40),
+  "LOGIN" VARCHAR(255),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(100),
+  "SALT" VARCHAR(40),
+  "ACTIVE" BOOLEAN DEFAULT TRUE,
+  "SCM_ACCOUNTS" VARCHAR(4000),
+  "EXTERNAL_IDENTITY" VARCHAR(255),
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "IS_ROOT" BOOLEAN NOT NULL,
+  "USER_LOCAL" BOOLEAN,
+  "ONBOARDED" BOOLEAN NOT NULL,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT,
+  "HOMEPAGE_TYPE" VARCHAR(40),
+  "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest/users.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest/users.sql
new file mode 100644 (file)
index 0000000..a63e0ae
--- /dev/null
@@ -0,0 +1,23 @@
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40),
+  "LOGIN" VARCHAR(255),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(100),
+  "SALT" VARCHAR(40),
+  "ACTIVE" BOOLEAN DEFAULT TRUE,
+  "SCM_ACCOUNTS" VARCHAR(4000),
+  "EXTERNAL_ID" VARCHAR(255),
+  "EXTERNAL_LOGIN" VARCHAR(255),
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "IS_ROOT" BOOLEAN NOT NULL,
+  "USER_LOCAL" BOOLEAN,
+  "ONBOARDED" BOOLEAN NOT NULL,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT,
+  "HOMEPAGE_TYPE" VARCHAR(40),
+  "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest/users.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest/users.sql
new file mode 100644 (file)
index 0000000..a63e0ae
--- /dev/null
@@ -0,0 +1,23 @@
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40),
+  "LOGIN" VARCHAR(255),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(100),
+  "SALT" VARCHAR(40),
+  "ACTIVE" BOOLEAN DEFAULT TRUE,
+  "SCM_ACCOUNTS" VARCHAR(4000),
+  "EXTERNAL_ID" VARCHAR(255),
+  "EXTERNAL_LOGIN" VARCHAR(255),
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "IS_ROOT" BOOLEAN NOT NULL,
+  "USER_LOCAL" BOOLEAN,
+  "ONBOARDED" BOOLEAN NOT NULL,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT,
+  "HOMEPAGE_TYPE" VARCHAR(40),
+  "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest/users.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest/users.sql
new file mode 100644 (file)
index 0000000..a63e0ae
--- /dev/null
@@ -0,0 +1,23 @@
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40),
+  "LOGIN" VARCHAR(255),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(100),
+  "SALT" VARCHAR(40),
+  "ACTIVE" BOOLEAN DEFAULT TRUE,
+  "SCM_ACCOUNTS" VARCHAR(4000),
+  "EXTERNAL_ID" VARCHAR(255),
+  "EXTERNAL_LOGIN" VARCHAR(255),
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "IS_ROOT" BOOLEAN NOT NULL,
+  "USER_LOCAL" BOOLEAN,
+  "ONBOARDED" BOOLEAN NOT NULL,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT,
+  "HOMEPAGE_TYPE" VARCHAR(40),
+  "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest/users.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest/users.sql
new file mode 100644 (file)
index 0000000..19dc45a
--- /dev/null
@@ -0,0 +1,23 @@
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40),
+  "LOGIN" VARCHAR(255),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(100),
+  "SALT" VARCHAR(40),
+  "ACTIVE" BOOLEAN DEFAULT TRUE,
+  "SCM_ACCOUNTS" VARCHAR(4000),
+  "EXTERNAL_ID" VARCHAR(255),
+  "EXTERNAL_IDENTITY" VARCHAR(255),
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "IS_ROOT" BOOLEAN NOT NULL,
+  "USER_LOCAL" BOOLEAN,
+  "ONBOARDED" BOOLEAN NOT NULL,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT,
+  "HOMEPAGE_TYPE" VARCHAR(40),
+  "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest/users.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest/users.sql
new file mode 100644 (file)
index 0000000..a63e0ae
--- /dev/null
@@ -0,0 +1,23 @@
+CREATE TABLE "USERS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "UUID" VARCHAR(40),
+  "LOGIN" VARCHAR(255),
+  "NAME" VARCHAR(200),
+  "EMAIL" VARCHAR(100),
+  "CRYPTED_PASSWORD" VARCHAR(100),
+  "SALT" VARCHAR(40),
+  "ACTIVE" BOOLEAN DEFAULT TRUE,
+  "SCM_ACCOUNTS" VARCHAR(4000),
+  "EXTERNAL_ID" VARCHAR(255),
+  "EXTERNAL_LOGIN" VARCHAR(255),
+  "EXTERNAL_IDENTITY_PROVIDER" VARCHAR(100),
+  "IS_ROOT" BOOLEAN NOT NULL,
+  "USER_LOCAL" BOOLEAN,
+  "ONBOARDED" BOOLEAN NOT NULL,
+  "CREATED_AT" BIGINT,
+  "UPDATED_AT" BIGINT,
+  "HOMEPAGE_TYPE" VARCHAR(40),
+  "HOMEPAGE_PARAMETER" VARCHAR(40)
+);
+CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
+CREATE INDEX "USERS_UPDATED_AT" ON "USERS" ("UPDATED_AT");
index 160d5021063b6cdd5daa93034f04d70e70f94236..4b369210c88c3004b0f674d5dd1cfe17f95a756b 100644 (file)
@@ -57,6 +57,11 @@ public class GroupTester {
     return session.wsClient().userGroups().create(request).getGroup();
   }
 
+  @SafeVarargs
+  public final UserGroups.Group generate(Consumer<CreateRequest>... populators) {
+    return generate(null, populators);
+  }
+
   public List<Group> getGroupsOfUser(@Nullable Organizations.Organization organization, String userLogin) {
     GroupsRequest request = new GroupsRequest()
       .setOrganization(organization != null ? organization.getKey() : null)
index 7e869f934afdac339c6f1453906a9ad8a3168fa3..5db4d5da72034723d2c9dec782cec1d2517f3229 100644 (file)
@@ -52,7 +52,7 @@ public class UserTester {
       .filter(u -> !"admin".equals(u.getLogin()))
       .forEach(u -> {
         PostRequest request = new PostRequest("api/users/deactivate").setParam("login", u.getLogin());
-        session.wsClient().wsConnector().call(request);
+        session.wsClient().wsConnector().call(request).failIfNotSuccessful();
       });
   }
 
index 8e127a28e3759a692b3999ad0f147d278d47d5d7..ec50b9d9ac3f5f76e95173899796a090d09bb552 100644 (file)
@@ -52,7 +52,7 @@ public class EmailAlreadyExistsException extends RuntimeException {
       encodeMessage(email),
       encodeMessage(userIdentity.getProviderLogin()),
       encodeMessage(provider.getKey()),
-      encodeMessage(existingUser.getExternalIdentity()),
+      encodeMessage(existingUser.getExternalLogin()),
       encodeMessage(existingUser.getExternalIdentityProvider()));
   }
 }
index 95db2536fb5ec30589361bb1c35cf33b85796b40..b0e18820180ade80e1e5d772b97f891a9cc1a78b 100644 (file)
@@ -27,6 +27,8 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import org.sonar.api.server.authentication.IdentityProvider;
 import org.sonar.api.server.authentication.UserIdentity;
 import org.sonar.api.utils.log.Logger;
@@ -88,47 +90,50 @@ public class UserIdentityAuthenticator {
     this.defaultGroupFinder = defaultGroupFinder;
   }
 
-  public UserDto authenticate(UserIdentity user, IdentityProvider provider, AuthenticationEvent.Source source, ExistingEmailStrategy existingEmailStrategy) {
+  public UserDto authenticate(UserIdentity userIdentity, IdentityProvider provider, AuthenticationEvent.Source source, ExistingEmailStrategy existingEmailStrategy) {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      String userLogin = user.getLogin();
-      UserDto userDto = dbClient.userDao().selectByLogin(dbSession, userLogin);
-      if (userDto != null && userDto.isActive()) {
-        registerExistingUser(dbSession, userDto, user, provider, source, existingEmailStrategy);
-        return userDto;
+      UserDto userDto = getUser(dbSession, userIdentity, provider);
+      if (userDto == null) {
+        return registerNewUser(dbSession, null, userIdentity, provider, source, existingEmailStrategy);
       }
-      return registerNewUser(dbSession, user, provider, source, existingEmailStrategy);
+      if (!userDto.isActive()) {
+        return registerNewUser(dbSession, userDto, userIdentity, provider, source, existingEmailStrategy);
+      }
+      return registerExistingUser(dbSession, userDto, userIdentity, provider, source, existingEmailStrategy);
     }
   }
 
-  private void registerExistingUser(DbSession dbSession, UserDto userDto, UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source,
+  @CheckForNull
+  private UserDto getUser(DbSession dbSession, UserIdentity userIdentity, IdentityProvider provider) {
+    String externalId = userIdentity.getProviderId();
+    UserDto user = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, externalId == null ? userIdentity.getProviderLogin() : externalId, provider.getKey());
+    // We need to search by login because :
+    // 1. external id may have not been set before,
+    // 2. user may have been provisioned,
+    // 3. user may have been disabled.
+    return user != null ? user : dbClient.userDao().selectByLogin(dbSession, userIdentity.getLogin());
+  }
+
+  private UserDto registerExistingUser(DbSession dbSession, UserDto userDto, UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source,
     ExistingEmailStrategy existingEmailStrategy) {
-    UpdateUser update = UpdateUser.create(userDto.getLogin())
+    UpdateUser update = new UpdateUser()
+      .setLogin(identity.getLogin())
       .setEmail(identity.getEmail())
       .setName(identity.getName())
-      .setExternalIdentity(new ExternalIdentity(provider.getKey(), identity.getProviderLogin()));
+      .setExternalIdentity(new ExternalIdentity(provider.getKey(), identity.getProviderLogin(), identity.getProviderId()));
     Optional<UserDto> otherUserToIndex = validateEmail(dbSession, identity, provider, source, existingEmailStrategy);
-    userUpdater.updateAndCommit(dbSession, update, u -> syncGroups(dbSession, identity, u), toArray(otherUserToIndex));
+    userUpdater.updateAndCommit(dbSession, userDto, update, u -> syncGroups(dbSession, identity, u), toArray(otherUserToIndex));
+    return userDto;
   }
 
-  private UserDto registerNewUser(DbSession dbSession, UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source,
+  private UserDto registerNewUser(DbSession dbSession, @Nullable UserDto disabledUser, UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source,
     ExistingEmailStrategy existingEmailStrategy) {
-    if (!provider.allowsUsersToSignUp()) {
-      throw AuthenticationException.newBuilder()
-        .setSource(source)
-        .setLogin(identity.getLogin())
-        .setMessage(format("User signup disabled for provider '%s'", provider.getKey()))
-        .setPublicMessage(format("'%s' users are not allowed to sign up", provider.getKey()))
-        .build();
-    }
     Optional<UserDto> otherUserToIndex = validateEmail(dbSession, identity, provider, source, existingEmailStrategy);
-    return userUpdater.createAndCommit(dbSession, NewUser.builder()
-      .setLogin(identity.getLogin())
-      .setEmail(identity.getEmail())
-      .setName(identity.getName())
-      .setExternalIdentity(new ExternalIdentity(provider.getKey(), identity.getProviderLogin()))
-      .build(),
-      u -> syncGroups(dbSession, identity, u),
-      toArray(otherUserToIndex));
+    NewUser newUser = createNewUser(identity, provider, source);
+    if (disabledUser == null) {
+      return userUpdater.createAndCommit(dbSession, newUser, u -> syncGroups(dbSession, identity, u), toArray(otherUserToIndex));
+    }
+    return userUpdater.reactivateAndCommit(dbSession, disabledUser, newUser, u -> syncGroups(dbSession, identity, u), toArray(otherUserToIndex));
   }
 
   private Optional<UserDto> validateEmail(DbSession dbSession, UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source,
@@ -138,7 +143,9 @@ public class UserIdentityAuthenticator {
       return Optional.empty();
     }
     UserDto existingUser = dbClient.userDao().selectByEmail(dbSession, email);
-    if (existingUser == null || existingUser.getLogin().equals(identity.getLogin())) {
+    if (existingUser == null
+      || Objects.equals(existingUser.getLogin(), identity.getLogin())
+      || (Objects.equals(existingUser.getExternalId(), identity.getProviderId()) && Objects.equals(existingUser.getExternalIdentityProvider(), provider.getKey()))) {
       return Optional.empty();
     }
     switch (existingEmailStrategy) {
@@ -209,8 +216,25 @@ public class UserIdentityAuthenticator {
     return organizationFlags.isEnabled(dbSession) ? Optional.empty() : Optional.of(defaultGroupFinder.findDefaultGroup(dbSession, defaultOrganizationProvider.get().getUuid()));
   }
 
+  private static NewUser createNewUser(UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source) {
+    if (!provider.allowsUsersToSignUp()) {
+      throw AuthenticationException.newBuilder()
+        .setSource(source)
+        .setLogin(identity.getLogin())
+        .setMessage(format("User signup disabled for provider '%s'", provider.getKey()))
+        .setPublicMessage(format("'%s' users are not allowed to sign up", provider.getKey()))
+        .build();
+    }
+    return NewUser.builder()
+      .setLogin(identity.getLogin())
+      .setEmail(identity.getEmail())
+      .setName(identity.getName())
+      .setExternalIdentity(new ExternalIdentity(provider.getKey(), identity.getProviderLogin(), identity.getProviderId()))
+      .build();
+  }
+
   private static UserDto[] toArray(Optional<UserDto> userDto) {
-    return userDto.map(u -> new UserDto[]{u}).orElse(new UserDto[]{});
+    return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {});
   }
 
 }
index 744a49f7291407a10b102df26a0feb661e3c704d..0ae0b6e22ac09a248e870cd4ed6fadc055e6b5d3 100644 (file)
@@ -137,7 +137,7 @@ public class SendIssueNotificationsStep implements ComputationStep {
   private void sendNewIssuesNotification(NewIssuesStatistics statistics, Component project, long analysisDate) {
     NewIssuesStatistics.Stats globalStatistics = statistics.globalStatistics();
     NewIssuesNotification notification = newIssuesNotificationFactory
-      .newNewIssuesNotication()
+      .newNewIssuesNotification()
       .setProject(project.getPublicKey(), project.getName(), getBranchName(), getPullRequest())
       .setProjectVersion(project.getReportAttributes().getVersion())
       .setAnalysisDate(new Date(analysisDate))
index 3d103ee6f8b300dbae3109f59406284b12a4dd3c..02ea9b8ed2e53195f09307d0973adb99750591df 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.issue.notification;
 
 import org.sonar.api.utils.Durations;
 import org.sonar.db.DbClient;
-import org.sonar.server.user.index.UserIndex;
 
 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_ASSIGNEE;
 
@@ -29,8 +28,8 @@ public class MyNewIssuesNotification extends NewIssuesNotification {
 
   public static final String MY_NEW_ISSUES_NOTIF_TYPE = "my-new-issues";
 
-  MyNewIssuesNotification(UserIndex userIndex, DbClient dbClient, Durations durations) {
-    super(MY_NEW_ISSUES_NOTIF_TYPE, userIndex, dbClient, durations);
+  MyNewIssuesNotification(DbClient dbClient, Durations durations) {
+    super(MY_NEW_ISSUES_NOTIF_TYPE, dbClient, durations);
   }
 
   public MyNewIssuesNotification setAssignee(String assignee) {
index df8c6bb943b60808d4cc734f99d990470633ba69..a2efec4813f18e2a52ccc54045a1e3e4f450ee79 100644 (file)
@@ -40,9 +40,8 @@ import org.sonar.db.DbSession;
 import org.sonar.db.RowNotFoundException;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.issue.notification.NewIssuesStatistics.Metric;
-import org.sonar.server.user.index.UserDoc;
-import org.sonar.server.user.index.UserIndex;
 
 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_BRANCH;
 import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate.FIELD_PROJECT_VERSION;
@@ -60,17 +59,15 @@ public class NewIssuesNotification extends Notification {
   private static final String LABEL = ".label";
   private static final String DOT = ".";
 
-  private final transient UserIndex userIndex;
   private final transient DbClient dbClient;
   private final transient Durations durations;
 
-  NewIssuesNotification(UserIndex userIndex, DbClient dbClient, Durations durations) {
-    this(TYPE, userIndex, dbClient, durations);
+  NewIssuesNotification(DbClient dbClient, Durations durations) {
+    this(TYPE, dbClient, durations);
   }
 
-  protected NewIssuesNotification(String type, UserIndex userIndex, DbClient dbClient, Durations durations) {
+  protected NewIssuesNotification(String type, DbClient dbClient, Durations durations) {
     super(type);
-    this.userIndex = userIndex;
     this.dbClient = dbClient;
     this.durations = durations;
   }
@@ -104,7 +101,7 @@ public class NewIssuesNotification extends Notification {
 
     try (DbSession dbSession = dbClient.openSession(false)) {
       setRuleTypeStatistics(stats);
-      setAssigneesStatistics(stats);
+      setAssigneesStatistics(dbSession, stats);
       setTagsStatistics(stats);
       setComponentsStatistics(dbSession, stats);
       setRuleStatistics(dbSession, stats);
@@ -168,13 +165,13 @@ public class NewIssuesNotification extends Notification {
     }
   }
 
-  private void setAssigneesStatistics(NewIssuesStatistics.Stats stats) {
+  private void setAssigneesStatistics(DbSession dbSession, NewIssuesStatistics.Stats stats) {
     Metric metric = Metric.ASSIGNEE;
     int i = 1;
     for (Map.Entry<String, MetricStatsInt> assigneeStats : fiveBiggest(stats.getDistributedMetricStats(metric), MetricStatsInt::getOnLeak)) {
       String login = assigneeStats.getKey();
-      UserDoc user = userIndex.getNullableByLogin(login);
-      String name = user == null ? login : user.name();
+      UserDto user = dbClient.userDao().selectByLogin(dbSession, login);
+      String name = user == null ? login : user.getName();
       setFieldValue(metric + DOT + i + LABEL, name);
       setFieldValue(metric + DOT + i + COUNT, String.valueOf(assigneeStats.getValue().getOnLeak()));
       i++;
index 8d509752642541064f129d73d1e85d434a9d1a67..d423c37d79db19ecb544543631e1df0dcaad6b35 100644 (file)
@@ -23,26 +23,23 @@ import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.Durations;
 import org.sonar.db.DbClient;
-import org.sonar.server.user.index.UserIndex;
 
 @ServerSide
 @ComputeEngineSide
 public class NewIssuesNotificationFactory {
-  private final UserIndex userIndex;
   private final DbClient dbClient;
   private final Durations durations;
 
-  public NewIssuesNotificationFactory(UserIndex userIndex, DbClient dbClient, Durations durations) {
-    this.userIndex = userIndex;
+  public NewIssuesNotificationFactory(DbClient dbClient, Durations durations) {
     this.dbClient = dbClient;
     this.durations = durations;
   }
 
   public MyNewIssuesNotification newMyNewIssuesNotification() {
-    return new MyNewIssuesNotification(userIndex, dbClient, durations);
+    return new MyNewIssuesNotification(dbClient, durations);
   }
 
-  public NewIssuesNotification newNewIssuesNotication() {
-    return new NewIssuesNotification(userIndex, dbClient, durations);
+  public NewIssuesNotification newNewIssuesNotification() {
+    return new NewIssuesNotification(dbClient, durations);
   }
 }
index f7c8158dfcca4d8d2cdcd1cdfe1130055af32df5..446642308d3a75096f11859b7ac75bf05b1ab15b 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.organization.BillingValidations;
 import org.sonar.server.organization.BillingValidationsProxy;
@@ -177,14 +178,15 @@ public class DeleteAction implements OrganizationsWsAction {
   }
 
   private void deleteOrganization(DbSession dbSession, OrganizationDto organization) {
-    Collection<String> logins = dbClient.organizationMemberDao().selectLoginsByOrganizationUuid(dbSession, organization.getUuid());
+    Collection<String> uuids = dbClient.organizationMemberDao().selectUserUuidsByOrganizationUuid(dbSession, organization.getUuid());
     dbClient.organizationMemberDao().deleteByOrganizationUuid(dbSession, organization.getUuid());
     dbClient.organizationDao().deleteByUuid(dbSession, organization.getUuid());
     dbClient.userDao().cleanHomepage(dbSession, organization);
     dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid())
       .forEach(wh -> dbClient.webhookDeliveryDao().deleteByWebhook(dbSession, wh));
     dbClient.webhookDao().deleteByOrganization(dbSession, organization);
-    userIndexer.commitAndIndexByLogins(dbSession, logins);
+    List<UserDto> users = dbClient.userDao().selectByUuids(dbSession, uuids);
+    userIndexer.commitAndIndex(dbSession, users);
   }
 
   private static void preventDeletionOfDefaultOrganization(String key, DefaultOrganization defaultOrganization) {
index cb3be8c7b095f15f9292edbc0418f57c20148c63..a56830bfc22fc6e193d1d5e13f1668bfe2a24f57 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.server.user;
 
+import javax.annotation.Nullable;
+
 import static java.util.Objects.requireNonNull;
 
 public class ExternalIdentity {
@@ -26,17 +28,23 @@ public class ExternalIdentity {
   public static final String SQ_AUTHORITY = "sonarqube";
 
   private String provider;
+  private String login;
   private String id;
 
-  public ExternalIdentity(String provider, String id) {
+  public ExternalIdentity(String provider, String login, @Nullable String id) {
     this.provider = requireNonNull(provider, "Identity provider cannot be null");
-    this.id = requireNonNull(id, "Identity id cannot be null");
+    this.login = requireNonNull(login, "Identity login cannot be null");
+    this.id = id == null ? login : id;
   }
 
   public String getProvider() {
     return provider;
   }
 
+  public String getLogin() {
+    return login;
+  }
+
   public String getId() {
     return id;
   }
index f7296e86081091a61608b0fe6ffcb1908c5a4b94..6078bbc1a14221c0f3dc8e8209b98ca594a7a5ef 100644 (file)
@@ -19,8 +19,6 @@
  */
 package org.sonar.server.user;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.util.List;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -34,21 +32,24 @@ public class UpdateUser {
   private String password;
   private ExternalIdentity externalIdentity;
 
+  private boolean loginChanged;
   private boolean nameChanged;
   private boolean emailChanged;
   private boolean scmAccountsChanged;
   private boolean passwordChanged;
   private boolean externalIdentityChanged;
 
-  private UpdateUser(String login) {
-    // No direct call to this constructor
-    this.login = login;
-  }
-
+  @CheckForNull
   public String login() {
     return login;
   }
 
+  public UpdateUser setLogin(@Nullable String login) {
+    this.login = login;
+    loginChanged = true;
+    return this;
+  }
+
   @CheckForNull
   public String name() {
     return name;
@@ -107,6 +108,10 @@ public class UpdateUser {
     return this;
   }
 
+  public boolean isLoginChanged() {
+    return loginChanged;
+  }
+
   public boolean isNameChanged() {
     return nameChanged;
   }
@@ -127,8 +132,4 @@ public class UpdateUser {
     return externalIdentityChanged;
   }
 
-  public static UpdateUser create(String login) {
-    checkNotNull(login);
-    return new UpdateUser(login);
-  }
 }
index cdfc0b80716debdab4cc9b5274ee0b713e5be6c6..7483cc7912eaffa01399b91772be6d5ccac50e25 100644 (file)
@@ -52,9 +52,9 @@ import static com.google.common.collect.Lists.newArrayList;
 import static java.lang.String.format;
 import static java.util.Arrays.stream;
 import static java.util.stream.Stream.concat;
+import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE;
 import static org.sonar.core.config.CorePropertyDefinitions.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
-import static org.sonar.server.ws.WsUtils.checkFound;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 
 @ServerSide
@@ -97,23 +97,19 @@ public class UserUpdater {
   }
 
   public UserDto createAndCommit(DbSession dbSession, NewUser newUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) {
-    String login = newUser.login();
-    UserDto userDto = dbClient.userDao().selectByLogin(dbSession, newUser.login());
-    if (userDto == null) {
-      userDto = saveUser(dbSession, createDto(dbSession, newUser));
-    } else {
-      reactivateUser(dbSession, userDto, login, newUser);
-    }
-    beforeCommit.accept(userDto);
-    userIndexer.commitAndIndex(dbSession, concat(Stream.of(userDto), stream(otherUsersToIndex)).collect(toList()));
+    UserDto userDto = saveUser(dbSession, createDto(dbSession, newUser));
+    return commitUser(dbSession, userDto, beforeCommit, otherUsersToIndex);
+  }
 
-    notifyNewUser(userDto.getLogin(), userDto.getName(), newUser.email());
-    return userDto;
+  public UserDto reactivateAndCommit(DbSession dbSession, UserDto disabledUser, NewUser newUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) {
+    checkArgument(!disabledUser.isActive(), "An active user with login '%s' already exists", disabledUser.getLogin());
+    reactivateUser(dbSession, disabledUser, newUser);
+    return commitUser(dbSession, disabledUser, beforeCommit, otherUsersToIndex);
   }
 
-  private void reactivateUser(DbSession dbSession, UserDto existingUser, String login, NewUser newUser) {
-    checkArgument(!existingUser.isActive(), "An active user with login '%s' already exists", login);
-    UpdateUser updateUser = UpdateUser.create(login)
+  private void reactivateUser(DbSession dbSession, UserDto disabledUser, NewUser newUser) {
+    UpdateUser updateUser = new UpdateUser()
+      .setLogin(newUser.login())
       .setName(newUser.name())
       .setEmail(newUser.email())
       .setScmAccounts(newUser.scmAccounts())
@@ -121,22 +117,18 @@ public class UserUpdater {
     if (newUser.password() != null) {
       updateUser.setPassword(newUser.password());
     }
-    setOnboarded(existingUser);
-    updateDto(dbSession, updateUser, existingUser);
-    updateUser(dbSession, existingUser);
-    addUserToDefaultOrganizationAndDefaultGroup(dbSession, existingUser);
+    setOnboarded(disabledUser);
+    updateDto(dbSession, updateUser, disabledUser);
+    updateUser(dbSession, disabledUser);
+    addUserToDefaultOrganizationAndDefaultGroup(dbSession, disabledUser);
   }
 
-  public void updateAndCommit(DbSession dbSession, UpdateUser updateUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) {
-    UserDto dto = dbClient.userDao().selectByLogin(dbSession, updateUser.login());
-    checkFound(dto, "User with login '%s' has not been found", updateUser.login());
+  public void updateAndCommit(DbSession dbSession, UserDto dto, UpdateUser updateUser, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) {
     boolean isUserUpdated = updateDto(dbSession, updateUser, dto);
     if (isUserUpdated) {
       // at least one change. Database must be updated and Elasticsearch re-indexed
       updateUser(dbSession, dto);
-      beforeCommit.accept(dto);
-      userIndexer.commitAndIndex(dbSession, concat(Stream.of(dto), stream(otherUsersToIndex)).collect(toList()));
-      notifyNewUser(dto.getLogin(), dto.getName(), dto.getEmail());
+      commitUser(dbSession, dto, beforeCommit, otherUsersToIndex);
     } else {
       // no changes but still execute the consumer
       beforeCommit.accept(dto);
@@ -144,12 +136,20 @@ public class UserUpdater {
     }
   }
 
+  private UserDto commitUser(DbSession dbSession, UserDto userDto, Consumer<UserDto> beforeCommit, UserDto... otherUsersToIndex) {
+    beforeCommit.accept(userDto);
+    userIndexer.commitAndIndex(dbSession, concat(Stream.of(userDto), stream(otherUsersToIndex)).collect(toList()));
+    notifyNewUser(userDto.getLogin(), userDto.getName(), userDto.getEmail());
+    return userDto;
+  }
+
   private UserDto createDto(DbSession dbSession, NewUser newUser) {
     UserDto userDto = new UserDto();
     List<String> messages = new ArrayList<>();
 
     String login = newUser.login();
     if (validateLoginFormat(login, messages)) {
+      checkLoginUniqueness(dbSession, login);
       userDto.setLogin(login);
     }
 
@@ -173,7 +173,7 @@ public class UserUpdater {
       userDto.setScmAccounts(scmAccounts);
     }
 
-    setExternalIdentity(userDto, newUser.externalIdentity());
+    setExternalIdentity(dbSession, userDto, newUser.externalIdentity());
     setOnboarded(userDto);
 
     checkRequest(messages.isEmpty(), messages);
@@ -182,15 +182,28 @@ public class UserUpdater {
 
   private boolean updateDto(DbSession dbSession, UpdateUser update, UserDto dto) {
     List<String> messages = newArrayList();
-    boolean changed = updateName(update, dto, messages);
+    boolean changed = updateLogin(dbSession, update, dto, messages);
+    changed |= updateName(update, dto, messages);
     changed |= updateEmail(update, dto, messages);
-    changed |= updateExternalIdentity(update, dto);
+    changed |= updateExternalIdentity(dbSession, update, dto);
     changed |= updatePassword(update, dto, messages);
     changed |= updateScmAccounts(dbSession, update, dto, messages);
     checkRequest(messages.isEmpty(), messages);
     return changed;
   }
 
+  private boolean updateLogin(DbSession dbSession, UpdateUser updateUser, UserDto userDto, List<String> messages) {
+    String newLogin = updateUser.login();
+    if (updateUser.isLoginChanged() && validateLoginFormat(newLogin, messages) && !Objects.equals(userDto.getLogin(), newLogin)) {
+      checkLoginUniqueness(dbSession, newLogin);
+      dbClient.propertiesDao().selectByKeyAndMatchingValue(dbSession, DEFAULT_ISSUE_ASSIGNEE, userDto.getLogin())
+        .forEach(p -> dbClient.propertiesDao().saveProperty(p.setValue(newLogin)));
+      userDto.setLogin(newLogin);
+      return true;
+    }
+    return false;
+  }
+
   private static boolean updateName(UpdateUser updateUser, UserDto userDto, List<String> messages) {
     String name = updateUser.name();
     if (updateUser.isNameChanged() && validateNameFormat(name, messages) && !Objects.equals(userDto.getName(), name)) {
@@ -209,10 +222,10 @@ public class UserUpdater {
     return false;
   }
 
-  private static boolean updateExternalIdentity(UpdateUser updateUser, UserDto userDto) {
+  private boolean updateExternalIdentity(DbSession dbSession, UpdateUser updateUser, UserDto userDto) {
     ExternalIdentity externalIdentity = updateUser.externalIdentity();
     if (updateUser.isExternalIdentityChanged() && !isSameExternalIdentity(userDto, externalIdentity)) {
-      setExternalIdentity(userDto, externalIdentity);
+      setExternalIdentity(dbSession, userDto, externalIdentity);
       return true;
     }
     return false;
@@ -247,22 +260,29 @@ public class UserUpdater {
 
   private static boolean isSameExternalIdentity(UserDto dto, @Nullable ExternalIdentity externalIdentity) {
     return externalIdentity != null
-      && Objects.equals(dto.getExternalIdentity(), externalIdentity.getId())
+      && !dto.isLocal()
+      && Objects.equals(dto.getExternalId(), externalIdentity.getId())
+      && Objects.equals(dto.getExternalLogin(), externalIdentity.getLogin())
       && Objects.equals(dto.getExternalIdentityProvider(), externalIdentity.getProvider());
   }
 
-  private static void setExternalIdentity(UserDto dto, @Nullable ExternalIdentity externalIdentity) {
+  private void setExternalIdentity(DbSession dbSession, UserDto dto, @Nullable ExternalIdentity externalIdentity) {
     if (externalIdentity == null) {
-      dto.setExternalIdentity(dto.getLogin());
+      dto.setExternalLogin(dto.getLogin());
       dto.setExternalIdentityProvider(SQ_AUTHORITY);
+      dto.setExternalId(dto.getLogin());
       dto.setLocal(true);
     } else {
-      dto.setExternalIdentity(externalIdentity.getId());
+      dto.setExternalLogin(externalIdentity.getLogin());
       dto.setExternalIdentityProvider(externalIdentity.getProvider());
+      dto.setExternalId(externalIdentity.getId());
       dto.setLocal(false);
       dto.setSalt(null);
       dto.setCryptedPassword(null);
     }
+    UserDto existingUser = dbClient.userDao().selectByExternalIdAndIdentityProvider(dbSession, dto.getExternalId(), dto.getExternalIdentityProvider());
+    checkArgument(existingUser == null || Objects.equals(dto.getUuid(), existingUser.getUuid()),
+      "A user with provider id '%s' and identity provider '%s' already exists", dto.getExternalId(), dto.getExternalIdentityProvider());
   }
 
   private void setOnboarded(UserDto userDto) {
@@ -364,6 +384,11 @@ public class UserUpdater {
     return Collections.emptyList();
   }
 
+  private void checkLoginUniqueness(DbSession dbSession, String login) {
+    UserDto existingUser = dbClient.userDao().selectByLogin(dbSession, login);
+    checkArgument(existingUser == null, "A user with login '%s' already exists", login);
+  }
+
   private UserDto saveUser(DbSession dbSession, UserDto userDto) {
     userDto.setActive(true);
     UserDto res = dbClient.userDao().insert(dbSession, userDto);
index 103e16cf0a7393c155cb67483fad80dac063229c..de49edd51b9141e4711a7ab3835dbe0cc774bf51 100644 (file)
@@ -40,7 +40,7 @@ public class UserDoc extends BaseDoc implements User {
 
   @Override
   public String getId() {
-    return login();
+    return uuid();
   }
 
   @Override
@@ -53,6 +53,10 @@ public class UserDoc extends BaseDoc implements User {
     return null;
   }
 
+  public String uuid() {
+    return getField(UserIndexDefinition.FIELD_UUID);
+  }
+
   @Override
   public String login() {
     return getField(UserIndexDefinition.FIELD_LOGIN);
@@ -82,6 +86,11 @@ public class UserDoc extends BaseDoc implements User {
     return getField(FIELD_ORGANIZATION_UUIDS);
   }
 
+  public UserDoc setUuid(@Nullable String s) {
+    setField(UserIndexDefinition.FIELD_UUID, s);
+    return this;
+  }
+
   public UserDoc setLogin(@Nullable String s) {
     setField(UserIndexDefinition.FIELD_LOGIN, s);
     return this;
index c33452c9cd2ff51378d34382191f7be04788852d..afc84a3c136ade15a664ceff417b899a3bfc81c5 100644 (file)
@@ -22,10 +22,7 @@ package org.sonar.server.user.index;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
-import javax.annotation.CheckForNull;
 import org.apache.commons.lang.StringUtils;
-import org.elasticsearch.action.get.GetRequestBuilder;
-import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.index.query.BoolQueryBuilder;
 import org.elasticsearch.index.query.Operator;
@@ -65,18 +62,6 @@ public class UserIndex {
     this.system2 = system2;
   }
 
-  @CheckForNull
-  public UserDoc getNullableByLogin(String login) {
-    GetRequestBuilder request = esClient.prepareGet(UserIndexDefinition.INDEX_TYPE_USER, login)
-      .setFetchSource(true)
-      .setRouting(login);
-    GetResponse response = request.get();
-    if (response.isExists()) {
-      return new UserDoc(response.getSource());
-    }
-    return null;
-  }
-
   /**
    * Returns the active users (at most 3) who are associated to the given SCM account. This method can be used
    * to detect user conflicts.
index a63f0a7c79f76afcebca8cfa9ce9f108851e4722..ce88c2d78e846e65281e230e6d38ff757c986aeb 100644 (file)
@@ -34,6 +34,7 @@ import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
 public class UserIndexDefinition implements IndexDefinition {
 
   public static final IndexType INDEX_TYPE_USER = new IndexType("users", "user");
+  public static final String FIELD_UUID = "uuid";
   public static final String FIELD_LOGIN = "login";
   public static final String FIELD_NAME = "name";
   public static final String FIELD_EMAIL = "email";
@@ -56,6 +57,7 @@ public class UserIndexDefinition implements IndexDefinition {
 
     // type "user"
     NewIndex.NewIndexType mapping = index.createType(INDEX_TYPE_USER.getType());
+    mapping.keywordFieldBuilder(FIELD_UUID).disableNorms().build();
     mapping.keywordFieldBuilder(FIELD_LOGIN).addSubFields(USER_SEARCH_GRAMS_ANALYZER).build();
     mapping.keywordFieldBuilder(FIELD_NAME).addSubFields(USER_SEARCH_GRAMS_ANALYZER).build();
     mapping.keywordFieldBuilder(FIELD_EMAIL).addSubFields(USER_SEARCH_GRAMS_ANALYZER, SORTABLE_ANALYZER).build();
index bc8706059b2d4e2e04919e21e7cc53f6f19c9fa5..6570b6e454781b46e19e0d67e143cda0bbdced22 100644 (file)
@@ -20,7 +20,6 @@
 package org.sonar.server.user.index;
 
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Collections2;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Maps;
@@ -44,6 +43,7 @@ import org.sonar.server.es.ResilientIndexer;
 
 import static java.util.Collections.singletonList;
 import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.server.user.index.UserIndexDefinition.INDEX_TYPE_USER;
 
 public class UserIndexer implements ResilientIndexer {
@@ -64,35 +64,32 @@ public class UserIndexer implements ResilientIndexer {
   @Override
   public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
     try (DbSession dbSession = dbClient.openSession(false)) {
-      ListMultimap<String, String> organizationUuidsByLogin = ArrayListMultimap.create();
-      dbClient.organizationMemberDao().selectAllForUserIndexing(dbSession, organizationUuidsByLogin::put);
+      ListMultimap<String, String> organizationUuidsByUserUuid = ArrayListMultimap.create();
+      dbClient.organizationMemberDao().selectAllForUserIndexing(dbSession, organizationUuidsByUserUuid::put);
 
       BulkIndexer bulkIndexer = newBulkIndexer(Size.LARGE, IndexingListener.FAIL_ON_ERROR);
       bulkIndexer.start();
       dbClient.userDao().scrollAll(dbSession,
         // only index requests, no deletion requests.
         // Deactivated users are not deleted but updated.
-        u -> bulkIndexer.add(newIndexRequest(u, organizationUuidsByLogin)));
+        u -> bulkIndexer.add(newIndexRequest(u, organizationUuidsByUserUuid)));
       bulkIndexer.stop();
     }
   }
 
   public void commitAndIndex(DbSession dbSession, UserDto user) {
-    commitAndIndexByLogins(dbSession, singletonList(user.getLogin()));
+    commitAndIndex(dbSession, singletonList(user));
   }
 
   public void commitAndIndex(DbSession dbSession, Collection<UserDto> users) {
-    commitAndIndexByLogins(dbSession, Collections2.transform(users, UserDto::getLogin));
-  }
-
-  public void commitAndIndexByLogins(DbSession dbSession, Collection<String> logins) {
-    List<EsQueueDto> items = logins.stream()
-      .map(l -> EsQueueDto.create(INDEX_TYPE_USER.format(), l))
+    List<String> uuids = users.stream().map(UserDto::getUuid).collect(toList());
+    List<EsQueueDto> items = uuids.stream()
+      .map(uuid -> EsQueueDto.create(INDEX_TYPE_USER.format(), uuid))
       .collect(MoreCollectors.toArrayList());
 
     dbClient.esQueueDao().insert(dbSession, items);
     dbSession.commit();
-    postCommit(dbSession, logins, items);
+    postCommit(dbSession, users.stream().map(UserDto::getLogin).collect(toList()), items);
   }
 
   /**
@@ -111,27 +108,27 @@ public class UserIndexer implements ResilientIndexer {
     if (items.isEmpty()) {
       return new IndexingResult();
     }
-    Set<String> logins = items
+    Set<String> uuids = items
       .stream()
       .map(EsQueueDto::getDocId)
       .collect(toHashSet(items.size()));
 
-    ListMultimap<String, String> organizationUuidsByLogin = ArrayListMultimap.create();
-    dbClient.organizationMemberDao().selectForUserIndexing(dbSession, logins, organizationUuidsByLogin::put);
+    ListMultimap<String, String> organizationUuidsByUserUuid = ArrayListMultimap.create();
+    dbClient.organizationMemberDao().selectForUserIndexing(dbSession, uuids, organizationUuidsByUserUuid::put);
 
     BulkIndexer bulkIndexer = newBulkIndexer(Size.REGULAR, new OneToOneResilientIndexingListener(dbClient, dbSession, items));
     bulkIndexer.start();
-    dbClient.userDao().scrollByLogins(dbSession, logins,
+    dbClient.userDao().scrollByUuids(dbSession, uuids,
       // only index requests, no deletion requests.
       // Deactivated users are not deleted but updated.
       u -> {
-        logins.remove(u.getLogin());
-        bulkIndexer.add(newIndexRequest(u, organizationUuidsByLogin));
+        uuids.remove(u.getUuid());
+        bulkIndexer.add(newIndexRequest(u, organizationUuidsByUserUuid));
       });
 
-    // the remaining logins reference rows that don't exist in db. They must
+    // the remaining uuids reference rows that don't exist in db. They must
     // be deleted from index.
-    logins.forEach(l -> bulkIndexer.addDeletion(INDEX_TYPE_USER, l));
+    uuids.forEach(uuid -> bulkIndexer.addDeletion(INDEX_TYPE_USER, uuid));
     return bulkIndexer.stop();
   }
 
@@ -139,15 +136,16 @@ public class UserIndexer implements ResilientIndexer {
     return new BulkIndexer(esClient, INDEX_TYPE_USER, bulkSize, listener);
   }
 
-  private static IndexRequest newIndexRequest(UserDto user, ListMultimap<String, String> organizationUuidsByLogins) {
+  private static IndexRequest newIndexRequest(UserDto user, ListMultimap<String, String> organizationUuidsByUserUuid) {
     UserDoc doc = new UserDoc(Maps.newHashMapWithExpectedSize(8));
     // all the keys must be present, even if value is null
+    doc.setUuid(user.getUuid());
     doc.setLogin(user.getLogin());
     doc.setName(user.getName());
     doc.setEmail(user.getEmail());
     doc.setActive(user.isActive());
     doc.setScmAccounts(UserDto.decodeScmAccounts(user.getScmAccounts()));
-    doc.setOrganizationUuids(organizationUuidsByLogins.get(user.getLogin()));
+    doc.setOrganizationUuids(organizationUuidsByUserUuid.get(user.getUuid()));
 
     return new IndexRequest(INDEX_TYPE_USER.getIndex(), INDEX_TYPE_USER.getType())
       .id(doc.getId())
index a8cfae3c843524270ec85add79ad5d48da8aa73c..ad35e69857ddcb3f57f9874634064bc90db34ee8 100644 (file)
@@ -28,10 +28,13 @@ import org.sonar.db.user.UserDto;
 import org.sonar.server.authentication.LocalAuthentication;
 import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.user.UpdateUser;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.user.UserUpdater;
 
+import static java.lang.String.format;
+
 public class ChangePasswordAction implements UsersWsAction {
 
   private static final String PARAM_LOGIN = "login";
@@ -82,23 +85,31 @@ public class ChangePasswordAction implements UsersWsAction {
 
     try (DbSession dbSession = dbClient.openSession(false)) {
       String login = request.mandatoryParam(PARAM_LOGIN);
+      UserDto user = getUser(dbSession, login);
       if (login.equals(userSession.getLogin())) {
         String previousPassword = request.mandatoryParam(PARAM_PREVIOUS_PASSWORD);
-        checkCurrentPassword(dbSession, login, previousPassword);
+        checkCurrentPassword(dbSession, user, previousPassword);
       } else {
         userSession.checkIsSystemAdministrator();
       }
 
       String password = request.mandatoryParam(PARAM_PASSWORD);
-      UpdateUser updateUser = UpdateUser.create(login).setPassword(password);
-
-      userUpdater.updateAndCommit(dbSession, updateUser, u -> {});
+      UpdateUser updateUser = new UpdateUser().setPassword(password);
+      userUpdater.updateAndCommit(dbSession, user, updateUser, u -> {
+      });
     }
     response.noContent();
   }
 
-  private void checkCurrentPassword(DbSession dbSession, String login, String password) {
-    UserDto user = dbClient.userDao().selectOrFailByLogin(dbSession, login);
+  private UserDto getUser(DbSession dbSession, String login) {
+    UserDto user = dbClient.userDao().selectByLogin(dbSession, login);
+    if (user == null || !user.isActive()) {
+      throw new NotFoundException(format("User with login '%s' has not been found", login));
+    }
+    return user;
+  }
+
+  private void checkCurrentPassword(DbSession dbSession, UserDto user, String password) {
     try {
       localAuthentication.authenticate(dbSession, user, password, AuthenticationEvent.Method.BASIC);
     } catch (AuthenticationException ex) {
index 96f91e244aebc14483cd418e3a732a46b261e5fa..adebf8d1a82ef926891841ae85571cbd29931887 100644 (file)
@@ -132,17 +132,24 @@ public class CreateAction implements UsersWsAction {
 
   private CreateWsResponse doHandle(CreateRequest request) {
     try (DbSession dbSession = dbClient.openSession(false)) {
+      String login = request.getLogin();
       NewUser.Builder newUser = NewUser.builder()
-        .setLogin(request.getLogin())
+        .setLogin(login)
         .setName(request.getName())
         .setEmail(request.getEmail())
         .setScmAccounts(request.getScmAccounts())
         .setPassword(request.getPassword());
       if (!request.isLocal()) {
-        newUser.setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, request.getLogin()));
+        newUser.setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, login, login));
       }
-      UserDto createdUser = userUpdater.createAndCommit(dbSession, newUser.build(), u -> {});
-      return buildResponse(createdUser);
+      UserDto existingUser = dbClient.userDao().selectByLogin(dbSession, login);
+      if (existingUser == null) {
+        return buildResponse(userUpdater.createAndCommit(dbSession, newUser.build(), u -> {
+        }));
+      }
+      checkArgument(!existingUser.isActive(), "An active user with login '%s' already exists", login);
+      return buildResponse(userUpdater.reactivateAndCommit(dbSession, existingUser, newUser.build(), u -> {
+      }));
     }
   }
 
index 599604ef1e1bb865fd75ace10b41a14ec757e880..d6bc1c92651375504c5cc036e7c213ee457c814e 100644 (file)
@@ -123,7 +123,7 @@ public class CurrentAction implements UsersWsAction {
       .setShowOnboardingTutorial(!user.isOnboarded());
     setNullable(emptyToNull(user.getEmail()), builder::setEmail);
     setNullable(emptyToNull(user.getEmail()), u -> builder.setAvatar(avatarResolver.create(user)));
-    setNullable(user.getExternalIdentity(), builder::setExternalIdentity);
+    setNullable(user.getExternalLogin(), builder::setExternalIdentity);
     setNullable(user.getExternalIdentityProvider(), builder::setExternalProvider);
     return builder.build();
   }
index b869f805f22493832feaad9e4198a7ff7bc1da1d..961b482565009a5ca934704478ca3329d8c67996 100644 (file)
@@ -150,7 +150,7 @@ public class SearchAction implements UsersWsAction {
       setIfNeeded(FIELD_AVATAR, fields, emptyToNull(user.getEmail()), u -> userBuilder.setAvatar(avatarResolver.create(user)));
       setIfNeeded(FIELD_ACTIVE, fields, user.isActive(), userBuilder::setActive);
       setIfNeeded(FIELD_LOCAL, fields, user.isLocal(), userBuilder::setLocal);
-      setIfNeeded(FIELD_EXTERNAL_IDENTITY, fields, user.getExternalIdentity(), userBuilder::setExternalIdentity);
+      setIfNeeded(FIELD_EXTERNAL_IDENTITY, fields, user.getExternalLogin(), userBuilder::setExternalIdentity);
       setIfNeeded(FIELD_EXTERNAL_PROVIDER, fields, user.getExternalIdentityProvider(), userBuilder::setExternalProvider);
       setIfNeeded(FIELD_TOKENS_COUNT, fields, tokensCount, userBuilder::setTokensCount);
       setIfNeeded(isNeeded(FIELD_SCM_ACCOUNTS, fields) && !user.getScmAccountsAsList().isEmpty(), user.getScmAccountsAsList(),
index 039517ed614935a37bef27cb6e74ecc184a08212..720a508c0667f21e7ac2eaa58cc62b4e681206fb 100644 (file)
@@ -33,20 +33,22 @@ import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.user.UpdateUser;
 import org.sonar.server.user.UserSession;
 import org.sonar.server.user.UserUpdater;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Strings.emptyToNull;
 import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.lang.String.format;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 import static org.sonar.server.user.UserUpdater.EMAIL_MAX_LENGTH;
 import static org.sonar.server.user.UserUpdater.LOGIN_MAX_LENGTH;
 import static org.sonar.server.user.UserUpdater.NAME_MAX_LENGTH;
 import static org.sonar.server.user.ws.EmailValidator.isValidIfPresent;
-import static org.sonar.server.ws.WsUtils.checkFound;
 import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_UPDATE;
 import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_EMAIL;
 import static org.sonarqube.ws.client.user.UsersWsParameters.PARAM_LOGIN;
@@ -113,14 +115,16 @@ public class UpdateAction implements UsersWsAction {
     UpdateRequest updateRequest = toWsRequest(request);
     checkArgument(isValidIfPresent(updateRequest.getEmail()), "Email '%s' is not valid", updateRequest.getEmail());
     try (DbSession dbSession = dbClient.openSession(false)) {
-      doHandle(dbSession, toWsRequest(request));
-      writeUser(dbSession, response, updateRequest.getLogin());
+      UserDto user = getUser(dbSession, updateRequest.getLogin());
+      doHandle(dbSession, updateRequest);
+      writeUser(dbSession, response, user.getUuid());
     }
   }
 
   private void doHandle(DbSession dbSession, UpdateRequest request) {
     String login = request.getLogin();
-    UpdateUser updateUser = UpdateUser.create(login);
+    UserDto user = getUser(dbSession, login);
+    UpdateUser updateUser = new UpdateUser();
     if (request.getName() != null) {
       updateUser.setName(request.getName());
     }
@@ -130,17 +134,25 @@ public class UpdateAction implements UsersWsAction {
     if (!request.getScmAccounts().isEmpty()) {
       updateUser.setScmAccounts(request.getScmAccounts());
     }
-    userUpdater.updateAndCommit(dbSession, updateUser, u -> {
+    userUpdater.updateAndCommit(dbSession, user, updateUser, u -> {
     });
   }
 
-  private void writeUser(DbSession dbSession, Response response, String login) {
+  private UserDto getUser(DbSession dbSession, String login) {
+    UserDto user = dbClient.userDao().selectByLogin(dbSession, login);
+    if (user == null || !user.isActive()) {
+      throw new NotFoundException(format("User '%s' doesn't exist", login));
+    }
+    return user;
+  }
+
+  private void writeUser(DbSession dbSession, Response response, String uuid) {
     try (JsonWriter json = response.newJsonWriter()) {
       json.beginObject();
       json.name("user");
-      Set<String> groups = new HashSet<>();
-      UserDto user = checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User '%s' doesn't exist", login);
-      groups.addAll(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(login)).get(login));
+      UserDto user = dbClient.userDao().selectByUuid(dbSession, uuid);
+      checkState(user != null, "User with uuid '%s' doesn't exist", uuid);
+      Set<String> groups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(uuid)).get(uuid));
       userWriter.write(json, user, groups, UserJsonWriter.FIELDS);
       json.endObject().close();
     }
index 63e3018b20e08318d643ffc646080923d966f5ac..cd9272341741639a5bf28afb50b1e5da946b2910 100644 (file)
@@ -66,7 +66,7 @@ public class UserJsonWriter {
       writeIfNeeded(json, user.getEmail(), FIELD_EMAIL, fields);
       writeIfNeeded(json, user.isActive(), FIELD_ACTIVE, fields);
       writeIfNeeded(json, user.isLocal(), FIELD_LOCAL, fields);
-      writeIfNeeded(json, user.getExternalIdentity(), FIELD_EXTERNAL_IDENTITY, fields);
+      writeIfNeeded(json, user.getExternalLogin(), FIELD_EXTERNAL_IDENTITY, fields);
       writeIfNeeded(json, user.getExternalIdentityProvider(), FIELD_EXTERNAL_PROVIDER, fields);
       writeGroupsIfNeeded(json, groups, fields);
       writeScmAccountsIfNeeded(json, fields, user);
index d129bc38a32664c936cc236b94365e1be02763ec..cc4ff5cd62bb56c1d7105252d748837e24c86de8 100644 (file)
@@ -218,7 +218,7 @@ public class InitFilterTest {
 
   @Test
   public void redirect_when_failing_because_of_EmailAlreadyExistException() throws Exception {
-    UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalIdentity("john.bitbucket").setExternalIdentityProvider("bitbucket");
+    UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalLogin("john.bitbucket").setExternalIdentityProvider("bitbucket");
     FailWithEmailAlreadyExistException identityProvider = new FailWithEmailAlreadyExistException("failing", existingUser);
     when(request.getRequestURI()).thenReturn("/sessions/init/" + identityProvider.getKey());
     identityProviderRepository.addIdentityProvider(identityProvider);
index eed583f2333946b1d294b0e2fef64572f8a838fd..63803d3d010a51dd03f26e19caea33134c826141 100644 (file)
@@ -29,7 +29,6 @@ import org.junit.rules.ExpectedException;
 import org.mindrot.jbcrypt.BCrypt;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
 import org.sonar.server.authentication.event.AuthenticationEvent;
 import org.sonar.server.authentication.event.AuthenticationException;
 
@@ -123,8 +122,8 @@ public class LocalAuthenticationTest {
     String salt = DigestUtils.sha1Hex(saltRandom);
 
     UserDto user = newUserDto()
-      .setHashMethod(SHA1.name())
       .setCryptedPassword(null)
+      .setHashMethod(SHA1.name())
       .setSalt(salt);
 
     expectedException.expect(AuthenticationException.class);
@@ -138,9 +137,9 @@ public class LocalAuthenticationTest {
     String password = randomAlphanumeric(60);
 
     UserDto user = newUserDto()
-      .setSalt(null)
       .setHashMethod(SHA1.name())
-      .setCryptedPassword(DigestUtils.sha1Hex("--0242b0b4c0a93ddfe09dd886de50bc25ba000b51--" + password + "--"));
+      .setCryptedPassword(DigestUtils.sha1Hex("--0242b0b4c0a93ddfe09dd886de50bc25ba000b51--" + password + "--"))
+      .setSalt(null);
 
     expectedException.expect(AuthenticationException.class);
     expectedException.expectMessage("null salt");
index 4165bfe1670d198718b08fbc7c7b90d0077e177a..dcdde1b1610f3179c05bb48710d828af2fbc03cd 100644 (file)
@@ -194,7 +194,7 @@ public class OAuth2CallbackFilterTest {
 
   @Test
   public void redirect_when_failing_because_of_EmailAlreadyExistException() throws Exception {
-    UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalIdentity("john.bitbucket").setExternalIdentityProvider("bitbucket");
+    UserDto existingUser = newUserDto().setEmail("john@email.com").setExternalLogin("john.bitbucket").setExternalIdentityProvider("bitbucket");
     FailWithEmailAlreadyExistException identityProvider = new FailWithEmailAlreadyExistException(existingUser);
     when(request.getRequestURI()).thenReturn("/oauth2/callback/" + identityProvider.getKey());
     identityProviderRepository.addIdentityProvider(identityProvider);
index 69bd660fe8ce630dcc368a0a47d22234021048c6..553935c8aece712278ea09a07ecce281d808084a 100644 (file)
@@ -89,7 +89,7 @@ public class SsoAuthenticatorTest {
     .setLogin(DEFAULT_LOGIN)
     .setName(DEFAULT_NAME)
     .setEmail(DEFAULT_EMAIL)
-    .setExternalIdentity(DEFAULT_LOGIN)
+    .setExternalLogin(DEFAULT_LOGIN)
     .setExternalIdentityProvider("sonarqube");
 
   private GroupDto group1;
@@ -423,7 +423,7 @@ public class SsoAuthenticatorTest {
     assertThat(userDto.isActive()).isTrue();
     assertThat(userDto.getName()).isEqualTo(expectedName);
     assertThat(userDto.getEmail()).isEqualTo(expectedEmail);
-    assertThat(userDto.getExternalIdentity()).isEqualTo(expectedLogin);
+    assertThat(userDto.getExternalLogin()).isEqualTo(expectedLogin);
     assertThat(userDto.getExternalIdentityProvider()).isEqualTo("sonarqube");
     verityUserGroups(expectedLogin, expectedGroups);
   }
index 7e6505bcbccfd9ae195f96287545ef53e603a204..19e6b95b3f3a7fc007f0ef8ea80f38174657231a 100644 (file)
@@ -60,6 +60,7 @@ public class UserIdentityAuthenticatorTest {
   private static String USER_LOGIN = "github-johndoo";
 
   private static UserIdentity USER_IDENTITY = UserIdentity.builder()
+    .setProviderId("ABCD")
     .setProviderLogin("johndoo")
     .setLogin(USER_LOGIN)
     .setName("John")
@@ -110,8 +111,9 @@ public class UserIdentityAuthenticatorTest {
     assertThat(user.isActive()).isTrue();
     assertThat(user.getName()).isEqualTo("John");
     assertThat(user.getEmail()).isEqualTo("john@email.com");
-    assertThat(user.getExternalIdentity()).isEqualTo("johndoo");
+    assertThat(user.getExternalLogin()).isEqualTo("johndoo");
     assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
+    assertThat(user.getExternalId()).isEqualTo("ABCD");
     assertThat(user.isRoot()).isFalse();
     checkGroupMembership(user);
   }
@@ -195,6 +197,23 @@ public class UserIdentityAuthenticatorTest {
     assertThat(existingUserReloaded.getEmail()).isNull();
   }
 
+  @Test
+  public void external_id_is_set_to_provider_login_when_null() {
+    organizationFlags.setEnabled(true);
+    UserIdentity newUser = UserIdentity.builder()
+      .setProviderId(null)
+      .setLogin("john")
+      .setProviderLogin("johndoo")
+      .setName("JOhn")
+      .build();
+
+    underTest.authenticate(newUser, IDENTITY_PROVIDER, Source.local(Method.BASIC), ALLOW);
+
+    assertThat(db.users().selectUserByLogin(newUser.getLogin()).get())
+      .extracting(UserDto::getLogin, UserDto::getExternalId, UserDto::getExternalLogin)
+      .contains("john", "johndoo", "johndoo");
+  }
+
   @Test
   public void throw_EmailAlreadyExistException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_WARN() {
     organizationFlags.setEnabled(true);
@@ -243,23 +262,84 @@ public class UserIdentityAuthenticatorTest {
   }
 
   @Test
-  public void authenticate_existing_user() {
+  public void authenticate_existing_user_matching_login() {
     db.users().insertUser(u -> u
       .setLogin(USER_LOGIN)
       .setName("Old name")
       .setEmail("Old email")
-      .setExternalIdentity("old identity")
+      .setExternalId("old id")
+      .setExternalLogin("old identity")
       .setExternalIdentityProvider("old provide"));
 
     underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.local(Method.BASIC), FORBID);
 
-    UserDto userDto = db.users().selectUserByLogin(USER_LOGIN).get();
-    assertThat(userDto.isActive()).isTrue();
-    assertThat(userDto.getName()).isEqualTo("John");
-    assertThat(userDto.getEmail()).isEqualTo("john@email.com");
-    assertThat(userDto.getExternalIdentity()).isEqualTo("johndoo");
-    assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github");
-    assertThat(userDto.isRoot()).isFalse();
+    assertThat(db.users().selectUserByLogin(USER_LOGIN).get())
+      .extracting(UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider, UserDto::isActive)
+      .contains("John", "john@email.com", "ABCD", "johndoo", "github", true);
+  }
+
+  @Test
+  public void authenticate_existing_user_matching_external_id() {
+    UserDto user = db.users().insertUser(u -> u
+      .setLogin("Old login")
+      .setName("Old name")
+      .setEmail("Old email")
+      .setExternalId(USER_IDENTITY.getProviderId())
+      .setExternalLogin("old identity")
+      .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
+
+    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.local(Method.BASIC), FORBID);
+
+    assertThat(db.users().selectUserByLogin("Old login")).isNotPresent();
+    assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+      .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
+        UserDto::isActive)
+      .contains(USER_LOGIN, "John", "john@email.com", "ABCD", "johndoo", "github", true);
+  }
+
+  @Test
+  public void authenticate_existing_user_and_update_only_login() {
+    UserDto user = db.users().insertUser(u -> u
+      .setLogin("old login")
+      .setName(USER_IDENTITY.getName())
+      .setEmail(USER_IDENTITY.getEmail())
+      .setExternalId(USER_IDENTITY.getProviderId())
+      .setExternalLogin("old identity")
+      .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
+
+    underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.local(Method.BASIC), FORBID);
+
+    assertThat(db.users().selectUserByLogin("Old login")).isNotPresent();
+    assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+      .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
+        UserDto::isActive)
+      .containsExactlyInAnyOrder(USER_LOGIN, USER_IDENTITY.getName(), USER_IDENTITY.getEmail(), USER_IDENTITY.getProviderId(), USER_IDENTITY.getProviderLogin(), IDENTITY_PROVIDER.getKey(),
+        true);
+  }
+
+  @Test
+  public void authenticate_existing_user_matching_login_when_external_id_is_null() {
+    UserDto user = db.users().insertUser(u -> u
+      .setLogin(USER_LOGIN)
+      .setName("Old name")
+      .setEmail("Old email")
+      .setExternalId("Old id")
+      .setExternalLogin("old identity")
+      .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
+
+    underTest.authenticate(UserIdentity.builder()
+      .setProviderId(null)
+      .setProviderLogin("johndoo")
+      .setLogin(USER_LOGIN)
+      .setName("John")
+      .setEmail("john@email.com")
+      .build(),
+      IDENTITY_PROVIDER, Source.local(Method.BASIC), FORBID);
+
+    assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+      .extracting(UserDto::getLogin, UserDto::getName, UserDto::getEmail, UserDto::getExternalId, UserDto::getExternalLogin, UserDto::getExternalIdentityProvider,
+        UserDto::isActive)
+      .contains(user.getLogin(), "John", "john@email.com", "johndoo", "johndoo", "github", true);
   }
 
   @Test
@@ -270,7 +350,8 @@ public class UserIdentityAuthenticatorTest {
       .setActive(false)
       .setName("Old name")
       .setEmail("Old email")
-      .setExternalIdentity("old identity")
+      .setExternalId("old id")
+      .setExternalLogin("old identity")
       .setExternalIdentityProvider("old provide"));
 
     underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.local(Method.BASIC_TOKEN), FORBID);
@@ -279,7 +360,8 @@ public class UserIdentityAuthenticatorTest {
     assertThat(userDto.isActive()).isTrue();
     assertThat(userDto.getName()).isEqualTo("John");
     assertThat(userDto.getEmail()).isEqualTo("john@email.com");
-    assertThat(userDto.getExternalIdentity()).isEqualTo("johndoo");
+    assertThat(userDto.getExternalId()).isEqualTo("ABCD");
+    assertThat(userDto.getExternalLogin()).isEqualTo("johndoo");
     assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github");
     assertThat(userDto.isRoot()).isFalse();
   }
index 47f29a6c2a2a5a2eea3ce51d6afa8b507890b9a2..65a9de16726d8e99a3d430b1e2b777bf8e1db556 100644 (file)
@@ -25,16 +25,16 @@ import org.junit.Test;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
 import org.sonar.server.computation.task.projectanalysis.analysis.Organization;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.user.index.UserIndex;
-import org.sonar.server.user.index.UserIndexDefinition;
+import org.sonar.server.user.index.UserIndexer;
 
 import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 
@@ -42,52 +42,41 @@ public class ScmAccountToUserLoaderTest {
 
   private static final String ORGANIZATION_UUID = "my-organization";
 
+  @Rule
+  public DbTester db = DbTester.create();
   @Rule
   public EsTester es = EsTester.create();
-
   @Rule
   public LogTester logTester = new LogTester();
 
   @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
-    .setOrganization(Organization.from(new OrganizationDto().setUuid(ORGANIZATION_UUID).setKey("Key").setName("Name").setDefaultQualityGateUuid("QGate")));
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
 
   @Test
   public void load_login_for_scm_account() {
-    UserDoc user = new UserDoc()
-      .setLogin("charlie")
-      .setName("Charlie")
-      .setEmail("charlie@hebdo.com")
-      .setActive(true)
-      .setScmAccounts(asList("charlie", "jesuis@charlie.com"))
-      .setOrganizationUuids(singletonList(ORGANIZATION_UUID));
-    es.putDocuments(UserIndexDefinition.INDEX_TYPE_USER.getIndex(), UserIndexDefinition.INDEX_TYPE_USER.getType(), user);
+    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("charlie", "jesuis@charlie.com")));
+    OrganizationDto organization = db.organizations().insert(o -> o.setUuid(ORGANIZATION_UUID));
+    analysisMetadataHolder.setOrganization(Organization.from(organization));
+    db.organizations().addMember(organization, user);
+    userIndexer.indexOnStartup(null);
 
     UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
     ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index, analysisMetadataHolder);
 
     assertThat(underTest.load("missing")).isNull();
-    assertThat(underTest.load("jesuis@charlie.com")).isEqualTo("charlie");
+    assertThat(underTest.load("jesuis@charlie.com")).isEqualTo(user.getLogin());
   }
 
   @Test
   public void warn_if_multiple_users_share_the_same_scm_account() {
-    UserDoc user1 = new UserDoc()
-      .setLogin("charlie")
-      .setName("Charlie")
-      .setEmail("charlie@hebdo.com")
-      .setActive(true)
-      .setScmAccounts(asList("charlie", "jesuis@charlie.com"))
-      .setOrganizationUuids(singletonList(ORGANIZATION_UUID));
-    es.putDocuments(UserIndexDefinition.INDEX_TYPE_USER.getIndex(), UserIndexDefinition.INDEX_TYPE_USER.getType(), user1);
-
-    UserDoc user2 = new UserDoc()
-      .setLogin("another.charlie")
-      .setName("Another Charlie")
-      .setActive(true)
-      .setScmAccounts(singletonList("charlie"))
-      .setOrganizationUuids(singletonList(ORGANIZATION_UUID));
-    es.putDocuments(UserIndexDefinition.INDEX_TYPE_USER.getIndex(), UserIndexDefinition.INDEX_TYPE_USER.getType(), user2);
+    UserDto user1 = db.users().insertUser(u -> u.setLogin("charlie").setScmAccounts(asList("charlie", "jesuis@charlie.com")));
+    UserDto user2 = db.users().insertUser(u -> u.setLogin("another.charlie").setScmAccounts(asList("charlie")));
+    OrganizationDto organization = db.organizations().insert(o -> o.setUuid(ORGANIZATION_UUID));
+    analysisMetadataHolder.setOrganization(Organization.from(organization));
+    db.organizations().addMember(organization, user1, user2);
+    userIndexer.indexOnStartup(null);
 
     UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
     ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index, analysisMetadataHolder);
index eacd042c8ca9d0ef8f557c4d498e6bf0daa76d6d..482ff328fd93fbb116c0e8ea6a1262b12008b214 100644 (file)
@@ -121,7 +121,7 @@ public class SendIssueNotificationsStepTest extends BaseStepTest {
     underTest = new SendIssueNotificationsStep(issueCache, ruleRepository, treeRootHolder, notificationService, analysisMetadataHolder,
       newIssuesNotificationFactory);
 
-    when(newIssuesNotificationFactory.newNewIssuesNotication()).thenReturn(newIssuesNotificationMock);
+    when(newIssuesNotificationFactory.newNewIssuesNotification()).thenReturn(newIssuesNotificationMock);
     when(newIssuesNotificationFactory.newMyNewIssuesNotification()).thenReturn(myNewIssuesNotificationMock);
   }
 
index 13935ab49c7e09a90c0f199f85076fc3e9e4535b..9bd4c69c75c9659d85e9a5711c62e2276dd38d13 100644 (file)
@@ -21,25 +21,16 @@ package org.sonar.server.issue.notification;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.Locale;
 import org.apache.commons.io.IOUtils;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.stubbing.Answer;
 import org.sonar.api.config.EmailSettings;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.notifications.Notification;
-import org.sonar.core.i18n.DefaultI18n;
 import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.server.user.index.UserDoc;
-import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.i18n.I18nRule;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.RULE;
@@ -48,27 +39,15 @@ import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG
 
 public class MyNewIssuesEmailTemplateTest {
 
-  MyNewIssuesEmailTemplate underTest;
-  DefaultI18n i18n;
-  UserIndex userIndex;
-  Date date;
-
-  @Before
-  public void setUp() {
-    EmailSettings settings = mock(EmailSettings.class);
-    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-    i18n = mock(DefaultI18n.class);
-    date = new Date();
-    userIndex = mock(UserIndex.class);
-    // returns the login passed in parameter
-    when(userIndex.getNullableByLogin(anyString()))
-      .thenAnswer((Answer<UserDoc>) invocationOnMock -> new UserDoc().setName((String) invocationOnMock.getArguments()[0]));
-    when(i18n.message(any(Locale.class), eq("issue.type.BUG"), anyString())).thenReturn("Bug");
-    when(i18n.message(any(Locale.class), eq("issue.type.CODE_SMELL"), anyString())).thenReturn("Code Smell");
-    when(i18n.message(any(Locale.class), eq("issue.type.VULNERABILITY"), anyString())).thenReturn("Vulnerability");
-
-    underTest = new MyNewIssuesEmailTemplate(settings, i18n);
-  }
+  @Rule
+  public I18nRule i18n = new I18nRule()
+    .put("issue.type.BUG", "Bug")
+    .put("issue.type.CODE_SMELL", "Code Smell")
+    .put("issue.type.VULNERABILITY", "Vulnerability");
+  private MapSettings settings = new MapSettings()
+    .setProperty("sonar.core.serverBaseURL", "http://nemo.sonarsource.org");
+
+  private MyNewIssuesEmailTemplate underTest = new MyNewIssuesEmailTemplate(new EmailSettings(settings.asConfig()), i18n);
 
   @Test
   public void no_format_if_not_the_correct_notif() {
index 90d10b1fdf6b221f50cf61644212ab4eb353df80..12d62f86b98f78c961bcb1cb2b8aec9c38aba4c7 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.server.issue.notification;
 import org.junit.Test;
 import org.sonar.api.utils.Durations;
 import org.sonar.db.DbClient;
-import org.sonar.server.user.index.UserIndex;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -30,7 +29,7 @@ import static org.sonar.server.issue.notification.AbstractNewIssuesEmailTemplate
 
 public class MyNewIssuesNotificationTest {
 
-  MyNewIssuesNotification underTest = new MyNewIssuesNotification(mock(UserIndex.class), mock(DbClient.class), mock(Durations.class));
+  MyNewIssuesNotification underTest = new MyNewIssuesNotification(mock(DbClient.class), mock(Durations.class));
 
   @Test
   public void set_assignee() {
index 0dabe249a9f5d12ee1ac0bad615ec8d394b68da1..18802f7a30aba69d9617d9787356caa259597ddc 100644 (file)
@@ -21,24 +21,16 @@ package org.sonar.server.issue.notification;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.util.Locale;
 import org.apache.commons.io.IOUtils;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.mockito.stubbing.Answer;
 import org.sonar.api.config.EmailSettings;
+import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.notifications.Notification;
-import org.sonar.core.i18n.DefaultI18n;
 import org.sonar.plugins.emailnotifications.api.EmailMessage;
-import org.sonar.server.user.index.UserDoc;
-import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.i18n.I18nRule;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
@@ -48,25 +40,15 @@ import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG
 
 public class NewIssuesEmailTemplateTest {
 
-  NewIssuesEmailTemplate template;
-  DefaultI18n i18n;
-  UserIndex userIndex;
-
-  @Before
-  public void setUp() {
-    EmailSettings settings = mock(EmailSettings.class);
-    when(settings.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org");
-    i18n = mock(DefaultI18n.class);
-    userIndex = mock(UserIndex.class);
-    // returns the login passed in parameter
-    when(userIndex.getNullableByLogin(anyString()))
-      .thenAnswer((Answer<UserDoc>) invocationOnMock -> new UserDoc().setName((String) invocationOnMock.getArguments()[0]));
-    when(i18n.message(any(Locale.class), eq("issue.type.CODE_SMELL"), anyString())).thenReturn("Code Smell");
-    when(i18n.message(any(Locale.class), eq("issue.type.VULNERABILITY"), anyString())).thenReturn("Vulnerability");
-    when(i18n.message(any(Locale.class), eq("issue.type.BUG"), anyString())).thenReturn("Bug");
-
-    template = new NewIssuesEmailTemplate(settings, i18n);
-  }
+  @Rule
+  public I18nRule i18n = new I18nRule()
+    .put("issue.type.BUG", "Bug")
+    .put("issue.type.CODE_SMELL", "Code Smell")
+    .put("issue.type.VULNERABILITY", "Vulnerability");
+  private MapSettings settings = new MapSettings()
+    .setProperty("sonar.core.serverBaseURL", "http://nemo.sonarsource.org");
+
+  private NewIssuesEmailTemplate template = new NewIssuesEmailTemplate(new EmailSettings(settings.asConfig()), i18n);
 
   @Test
   public void no_format_is_not_the_correct_notification() {
index 819bdc2f9bf4c82d6744f5111883f44632da45e3..14809eddac6367426c129295a2e5377a9c459aa7 100644 (file)
  */
 package org.sonar.server.issue.notification;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import java.util.Arrays;
-import java.util.Collections;
 import java.util.Date;
-import java.util.Random;
-import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.RuleType;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.Duration;
 import org.sonar.api.utils.Durations;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDao;
+import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.db.rule.RuleDao;
+import org.sonar.db.issue.IssueDto;
 import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.server.user.index.UserIndex;
+import org.sonar.db.user.UserDto;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyCollection;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.sonar.api.rules.RuleType.BUG;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.ASSIGNEE;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.COMPONENT;
 import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.EFFORT;
@@ -62,24 +49,10 @@ import static org.sonar.server.issue.notification.NewIssuesStatistics.Metric.TAG
 
 public class NewIssuesNotificationTest {
 
-  private final Random random = new Random();
-  private final RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
-  private NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-  private UserIndex userIndex = mock(UserIndex.class);
-  private DbClient dbClient = mock(DbClient.class);
-  private DbSession dbSession = mock(DbSession.class);
-  private ComponentDao componentDao = mock(ComponentDao.class);
-  private RuleDao ruleDao = mock(RuleDao.class);
-  private Durations durations = mock(Durations.class);
-  private NewIssuesNotification underTest = new NewIssuesNotification(userIndex, dbClient, durations);
-
-  @Before
-  public void setUp() throws Exception {
-    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
-    when(dbClient.componentDao()).thenReturn(componentDao);
-    when(dbClient.ruleDao()).thenReturn(ruleDao);
-    when(componentDao.selectByUuids(same(dbSession), anyCollection())).thenReturn(Collections.emptyList());
-  }
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private NewIssuesNotification underTest = new NewIssuesNotification(db.getDbClient(), new Durations());
 
   @Test
   public void set_project_without_branch() {
@@ -122,7 +95,6 @@ public class NewIssuesNotificationTest {
     underTest.setProjectVersion(null);
 
     assertThat(underTest.getFieldValue(NewIssuesEmailTemplate.FIELD_PROJECT_VERSION)).isNull();
-
   }
 
   @Test
@@ -136,19 +108,18 @@ public class NewIssuesNotificationTest {
 
   @Test
   public void set_statistics() {
-    addIssueNTimes(newIssue1(), 5);
-    addIssueNTimes(newIssue2(), 3);
-    when(componentDao.selectByUuids(dbSession, ImmutableSet.of("file-uuid", "directory-uuid")))
-      .thenReturn(Arrays.asList(
-        new ComponentDto().setUuid("file-uuid").setName("file-name"),
-        new ComponentDto().setUuid("directory-uuid").setName("directory-name")));
-    RuleKey rule1 = RuleKey.of("SonarQube", "rule-the-world");
-    RuleKey rule2 = RuleKey.of("SonarQube", "rule-the-universe");
-    when(ruleDao.selectDefinitionByKeys(dbSession, ImmutableSet.of(rule1, rule2)))
-      .thenReturn(
-        ImmutableList.of(newRule(rule1, "Rule the World", "Java"), newRule(rule2, "Rule the Universe", "Clojure")));
-
-    underTest.setStatistics("project-long-name", stats);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto directory = db.components().insertComponent(newDirectory(project, "path"));
+    ComponentDto file = db.components().insertComponent(newFileDto(directory));
+    RuleDefinitionDto rule1 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-world").setName("Rule the World").setLanguage("Java"));
+    RuleDefinitionDto rule2 = db.rules().insert(r -> r.setRepositoryKey("SonarQube").setRuleKey("rule1-the-universe").setName("Rule the Universe").setLanguage("Clojure"));
+    IssueDto issue1 = db.issues().insert(rule1, project, file, i -> i.setType(BUG).setAssignee("maynard").setTags(asList("bug", "owasp")));
+    IssueDto issue2 = db.issues().insert(rule2, project, directory, i -> i.setType(CODE_SMELL).setAssignee("keenan").setTags(singletonList("owasp")));
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
+
+    underTest.setStatistics(project.longName(), stats);
 
     assertThat(underTest.getFieldValue(RULE_TYPE + ".BUG.count")).isEqualTo("5");
     assertThat(underTest.getFieldValue(RULE_TYPE + ".CODE_SMELL.count")).isEqualTo("3");
@@ -160,130 +131,150 @@ public class NewIssuesNotificationTest {
     assertThat(underTest.getFieldValue(TAG + ".1.count")).isEqualTo("8");
     assertThat(underTest.getFieldValue(TAG + ".2.label")).isEqualTo("bug");
     assertThat(underTest.getFieldValue(TAG + ".2.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo("file-name");
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(file.name());
     assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("5");
-    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo("directory-name");
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo(directory.name());
     assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isEqualTo("3");
     assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo("Rule the World (Java)");
     assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("5");
     assertThat(underTest.getFieldValue(RULE + ".2.label")).isEqualTo("Rule the Universe (Clojure)");
     assertThat(underTest.getFieldValue(RULE + ".2.count")).isEqualTo("3");
-    assertThat(underTest.getDefaultMessage()).startsWith("8 new issues on project-long-name");
+    assertThat(underTest.getDefaultMessage()).startsWith("8 new issues on " + project.longName());
+  }
+
+  @Test
+  public void set_assignee() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = db.rules().insert();
+    UserDto user = db.users().insertUser();
+    IssueDto issue1 = db.issues().insert(rule, project, file, i -> i.setAssignee(user.getLogin()));
+    IssueDto issue2 = db.issues().insert(rule, project, file, i -> i.setAssignee("no_user"));
+    NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(issue1.toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(issue2.toDefaultIssue()));
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo(user.getName());
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("5");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo("no_user");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("3");
   }
 
   @Test
   public void add_only_5_assignees_with_biggest_issue_counts() {
-    String[] assignees = IntStream.range(0, 6 + random.nextInt(10)).mapToObj(s -> "assignee" + s).toArray(String[]::new);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDefinitionDto rule = db.rules().insert();
     NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    int i = assignees.length;
-    for (String assignee : assignees) {
-      IntStream.range(0, i).mapToObj(j -> new DefaultIssue().setType(randomRuleType).setAssignee(assignee)).forEach(stats::add);
-      i--;
-    }
-
-    underTest.setStatistics(randomAlphanumeric(20), stats);
-
-    for (int j = 0; j < 5; j++) {
-      String fieldBase = ASSIGNEE + "." + (j + 1);
-      assertThat(underTest.getFieldValue(fieldBase + ".label")).as("label of %s", fieldBase).isEqualTo(assignees[j]);
-      assertThat(underTest.getFieldValue(fieldBase + ".count")).as("count of %s", fieldBase).isEqualTo(String.valueOf(assignees.length - j));
-    }
+    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_1")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_2")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_3")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_4")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_5")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_6")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_7")).toDefaultIssue()));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule, project, file, issue -> issue.setAssignee("assignee_8")).toDefaultIssue()));
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.label")).isEqualTo("assignee_1");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".1.count")).isEqualTo("10");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.label")).isEqualTo("assignee_2");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".2.count")).isEqualTo("9");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".3.label")).isEqualTo("assignee_3");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".3.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".4.label")).isEqualTo("assignee_4");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".4.count")).isEqualTo("7");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".5.label")).isEqualTo("assignee_5");
+    assertThat(underTest.getFieldValue(ASSIGNEE + ".5.count")).isEqualTo("6");
     assertThat(underTest.getFieldValue(ASSIGNEE + ".6.label")).isNull();
     assertThat(underTest.getFieldValue(ASSIGNEE + ".6.count")).isNull();
   }
 
   @Test
   public void add_only_5_components_with_biggest_issue_counts() {
-    String[] componentUuids = IntStream.range(0, 6 + random.nextInt(10)).mapToObj(s -> "component_uuid_" + s).toArray(String[]::new);
+    ComponentDto project = db.components().insertPrivateProject();
+    RuleDefinitionDto rule = db.rules().insert();
     NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    int i = componentUuids.length;
-    for (String component : componentUuids) {
-      IntStream.range(0, i).mapToObj(j -> new DefaultIssue().setType(randomRuleType).setComponentUuid(component)).forEach(stats::add);
-      i--;
-    }
-    when(componentDao.selectByUuids(dbSession, Arrays.stream(componentUuids).limit(5).collect(Collectors.toSet())))
-      .thenReturn(
-        Arrays.stream(componentUuids).map(uuid -> new ComponentDto().setUuid(uuid).setName("name_" + uuid)).collect(MoreCollectors.toList()));
-
-    underTest.setStatistics(randomAlphanumeric(20), stats);
-
-    for (int j = 0; j < 5; j++) {
-      String fieldBase = COMPONENT + "." + (j + 1);
-      assertThat(underTest.getFieldValue(fieldBase + ".label")).as("label of %s", fieldBase).isEqualTo("name_" + componentUuids[j]);
-      assertThat(underTest.getFieldValue(fieldBase + ".count")).as("count of %s", fieldBase).isEqualTo(String.valueOf(componentUuids.length - j));
-    }
+    ComponentDto file1 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule, project, file1).toDefaultIssue()));
+    ComponentDto file2 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule, project, file2).toDefaultIssue()));
+    ComponentDto file3 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule, project, file3).toDefaultIssue()));
+    ComponentDto file4 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule, project, file4).toDefaultIssue()));
+    ComponentDto file5 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule, project, file5).toDefaultIssue()));
+    ComponentDto file6 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule, project, file6).toDefaultIssue()));
+    ComponentDto file7 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule, project, file7).toDefaultIssue()));
+    ComponentDto file8 = db.components().insertComponent(newFileDto(project));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule, project, file8).toDefaultIssue()));
+
+    underTest.setStatistics(project.longName(), stats);
+
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.label")).isEqualTo(file1.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".1.count")).isEqualTo("10");
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.label")).isEqualTo(file2.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".2.count")).isEqualTo("9");
+    assertThat(underTest.getFieldValue(COMPONENT + ".3.label")).isEqualTo(file3.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".3.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(COMPONENT + ".4.label")).isEqualTo(file4.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".4.count")).isEqualTo("7");
+    assertThat(underTest.getFieldValue(COMPONENT + ".5.label")).isEqualTo(file5.name());
+    assertThat(underTest.getFieldValue(COMPONENT + ".5.count")).isEqualTo("6");
     assertThat(underTest.getFieldValue(COMPONENT + ".6.label")).isNull();
     assertThat(underTest.getFieldValue(COMPONENT + ".6.count")).isNull();
   }
 
   @Test
   public void add_only_5_rules_with_biggest_issue_counts() {
-    String repository = randomAlphanumeric(4);
-    String[] ruleKeys = IntStream.range(0, 6 + random.nextInt(10)).mapToObj(s -> "rule_" + s).toArray(String[]::new);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
     NewIssuesStatistics.Stats stats = new NewIssuesStatistics.Stats(i -> true);
-    int i = ruleKeys.length;
-    for (String ruleKey : ruleKeys) {
-      IntStream.range(0, i).mapToObj(j -> new DefaultIssue().setType(randomRuleType).setRuleKey(RuleKey.of(repository, ruleKey))).forEach(stats::add);
-      i--;
-    }
-    when(ruleDao.selectDefinitionByKeys(dbSession, Arrays.stream(ruleKeys).limit(5).map(s -> RuleKey.of(repository, s)).collect(MoreCollectors.toSet(5))))
-      .thenReturn(
-        Arrays.stream(ruleKeys).limit(5).map(ruleKey -> new RuleDefinitionDto()
-          .setRuleKey(RuleKey.of(repository, ruleKey))
-          .setName("name_" + ruleKey)
-          .setLanguage("language_" + ruleKey))
-          .collect(MoreCollectors.toList(5)));
-
-    underTest.setStatistics(randomAlphanumeric(20), stats);
-
-    for (int j = 0; j < 5; j++) {
-      String fieldBase = RULE + "." + (j + 1);
-      assertThat(underTest.getFieldValue(fieldBase + ".label")).as("label of %s", fieldBase).isEqualTo("name_" + ruleKeys[j] + " (language_" + ruleKeys[j] + ")");
-      assertThat(underTest.getFieldValue(fieldBase + ".count")).as("count of %s", fieldBase).isEqualTo(String.valueOf(ruleKeys.length - j));
-    }
+    RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 10).forEach(i -> stats.add(db.issues().insert(rule1, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 9).forEach(i -> stats.add(db.issues().insert(rule2, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 8).forEach(i -> stats.add(db.issues().insert(rule3, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule4 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 7).forEach(i -> stats.add(db.issues().insert(rule4, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule5 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 6).forEach(i -> stats.add(db.issues().insert(rule5, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule6 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 5).forEach(i -> stats.add(db.issues().insert(rule6, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule7 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 4).forEach(i -> stats.add(db.issues().insert(rule7, project, file).toDefaultIssue()));
+    RuleDefinitionDto rule8 = db.rules().insert(r -> r.setLanguage("Java"));
+    IntStream.rangeClosed(1, 3).forEach(i -> stats.add(db.issues().insert(rule8, project, file).toDefaultIssue()));
+
+    underTest.setStatistics(project.longName(), stats);
+
+    String javaSuffix = " (Java)";
+    assertThat(underTest.getFieldValue(RULE + ".1.label")).isEqualTo(rule1.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".1.count")).isEqualTo("10");
+    assertThat(underTest.getFieldValue(RULE + ".2.label")).isEqualTo(rule2.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".2.count")).isEqualTo("9");
+    assertThat(underTest.getFieldValue(RULE + ".3.label")).isEqualTo(rule3.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".3.count")).isEqualTo("8");
+    assertThat(underTest.getFieldValue(RULE + ".4.label")).isEqualTo(rule4.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".4.count")).isEqualTo("7");
+    assertThat(underTest.getFieldValue(RULE + ".5.label")).isEqualTo(rule5.getName() + javaSuffix);
+    assertThat(underTest.getFieldValue(RULE + ".5.count")).isEqualTo("6");
     assertThat(underTest.getFieldValue(RULE + ".6.label")).isNull();
     assertThat(underTest.getFieldValue(RULE + ".6.count")).isNull();
   }
 
   @Test
   public void set_debt() {
-    when(durations.format(any(Duration.class))).thenReturn("55 min");
-
     underTest.setDebt(Duration.create(55));
 
-    assertThat(underTest.getFieldValue(EFFORT + ".count")).isEqualTo("55 min");
-  }
-
-  private void addIssueNTimes(DefaultIssue issue, int times) {
-    for (int i = 0; i < times; i++) {
-      stats.add(issue);
-    }
+    assertThat(underTest.getFieldValue(EFFORT + ".count")).isEqualTo("55min");
   }
 
-  private DefaultIssue newIssue1() {
-    return new DefaultIssue()
-      .setAssignee("maynard")
-      .setComponentUuid("file-uuid")
-      .setType(RuleType.BUG)
-      .setTags(Lists.newArrayList("bug", "owasp"))
-      .setRuleKey(RuleKey.of("SonarQube", "rule-the-world"))
-      .setEffort(Duration.create(5L));
-  }
-
-  private DefaultIssue newIssue2() {
-    return new DefaultIssue()
-      .setAssignee("keenan")
-      .setComponentUuid("directory-uuid")
-      .setType(RuleType.CODE_SMELL)
-      .setTags(Lists.newArrayList("owasp"))
-      .setRuleKey(RuleKey.of("SonarQube", "rule-the-universe"))
-      .setEffort(Duration.create(10L));
-  }
-
-  private RuleDefinitionDto newRule(RuleKey ruleKey, String name, String language) {
-    return new RuleDefinitionDto()
-      .setRuleKey(ruleKey)
-      .setName(name)
-      .setLanguage(language);
-  }
 }
index ccc3aff971a0268131d0c29f825e1ef93f7bf664..7e7e89fce2c608edf9c67449e1a17b89b6c88a6b 100644 (file)
@@ -51,7 +51,6 @@ import org.sonar.db.protobuf.DbIssues;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
 import org.sonar.server.es.StartupIndexer;
@@ -77,7 +76,6 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.groups.Tuple.tuple;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
 import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_BRANCH;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_MODE_DEBT;
 import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
@@ -570,7 +568,7 @@ public class SearchActionTest {
 
   @Test
   public void filter_by_assigned_to_me() {
-    dbClient.userDao().insert(session, newUserDto().setLogin("john").setName("John").setEmail("john@email.com"));
+    db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com"));
 
     ComponentDto project = insertComponent(ComponentTesting.newPublicProjectDto(defaultOrganization, "PROJECT_ID").setDbKey("PROJECT_KEY"));
     indexPermissions();
@@ -644,7 +642,7 @@ public class SearchActionTest {
 
   @Test
   public void assigned_to_me_facet_is_sticky_relative_to_assignees() {
-    dbClient.userDao().insert(session, newUserDto().setLogin("alice").setName("Alice").setEmail("alice@email.com"));
+    db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com"));
 
     ComponentDto project = insertComponent(ComponentTesting.newPublicProjectDto(otherOrganization2, "PROJECT_ID").setDbKey("PROJECT_KEY"));
     indexPermissions();
index 495a4c15c10a576127ee4b376166b260780f3b1d..1b8ef114ca7603576a8e665c9a4d6ac8a5169494 100644 (file)
@@ -40,8 +40,6 @@ import org.sonar.db.metric.MetricDto;
 import org.sonar.db.metric.MetricTesting;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.permission.OrganizationPermission;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
 import org.sonar.server.component.TestComponentFinder;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
@@ -60,7 +58,6 @@ import static org.sonar.api.measures.Metric.ValueType.INT;
 import static org.sonar.api.measures.Metric.ValueType.LEVEL;
 import static org.sonar.api.measures.Metric.ValueType.STRING;
 import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
-import static org.sonar.db.user.UserTesting.newUserDto;
 import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
 
 public class CreateActionTest {
@@ -87,12 +84,10 @@ public class CreateActionTest {
     ws = new WsTester(new CustomMeasuresWs(new CreateAction(dbClient, userSession, System2.INSTANCE, new CustomMeasureValidator(newFullTypeValidations()),
       new CustomMeasureJsonWriter(new UserJsonWriter(userSession)), TestComponentFinder.from(db))));
 
-    db.getDbClient().userDao().insert(dbSession, newUserDto()
-      .setLogin("login")
+    db.users().insertUser(u -> u.setLogin("login")
       .setName("Login")
       .setEmail("login@login.com")
       .setActive(true));
-    dbSession.commit();
 
     OrganizationDto organizationDto = db.organizations().insert();
     project = ComponentTesting.newPrivateProjectDto(organizationDto, DEFAULT_PROJECT_UUID).setDbKey(DEFAULT_PROJECT_KEY);
index 405a7aafed3cb89c979ec3ab99a211be267a4878..dd7a9e818c46be2c976f13b79e1a7599af5502f5 100644 (file)
@@ -37,8 +37,6 @@ import org.sonar.server.component.TestComponentFinder;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.user.index.UserDoc;
-import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -47,7 +45,6 @@ import static org.sonar.db.metric.MetricTesting.newMetricDto;
 import static org.sonar.server.measure.custom.ws.CustomMeasuresWs.ENDPOINT;
 import static org.sonar.server.measure.custom.ws.MetricsAction.ACTION;
 
-
 public class MetricsActionTest {
   private static final String DEFAULT_PROJECT_UUID = "project-uuid";
   private static final String DEFAULT_PROJECT_KEY = "project-key";
@@ -63,19 +60,14 @@ public class MetricsActionTest {
 
   private final DbClient dbClient = db.getDbClient();
   private final DbSession dbSession = db.getSession();
-  private WsTester ws;
   private ComponentDto defaultProject;
+  private WsTester ws;
 
   @Before
   public void setUp() throws Exception {
-    es.putDocuments(UserIndexDefinition.INDEX_TYPE_USER.getIndex(), UserIndexDefinition.INDEX_TYPE_USER.getType(), new UserDoc()
-      .setLogin("login")
-      .setName("Login")
-      .setEmail("login@login.com")
-      .setActive(true));
-    ws = new WsTester(new CustomMeasuresWs(new MetricsAction(dbClient, userSession, TestComponentFinder.from(db))));
     defaultProject = insertDefaultProject();
     userSession.logIn().addProjectPermission(UserRole.ADMIN, defaultProject);
+    ws = new WsTester(new CustomMeasuresWs(new MetricsAction(dbClient, userSession, TestComponentFinder.from(db))));
   }
 
   @Test
index dba9e84de3018f32a1b0dcea4c2faccdefde4b3b..adff6e1b9339ffc2d9b4444a03fb774eb1c0a063 100644 (file)
@@ -38,8 +38,6 @@ import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.component.SnapshotTesting;
 import org.sonar.db.measure.custom.CustomMeasureDto;
 import org.sonar.db.metric.MetricDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
 import org.sonar.server.component.TestComponentFinder;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -51,7 +49,6 @@ import org.sonar.server.ws.WsTester;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.db.measure.custom.CustomMeasureTesting.newCustomMeasureDto;
 import static org.sonar.db.metric.MetricTesting.newMetricDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
 
 public class SearchActionTest {
 
@@ -79,14 +76,10 @@ public class SearchActionTest {
     ws = new WsTester(new CustomMeasuresWs(new SearchAction(dbClient, customMeasureJsonWriter, userSessionRule, TestComponentFinder.from(db))));
     defaultProject = insertDefaultProject();
     userSessionRule.logIn().addProjectPermission(UserRole.ADMIN, defaultProject);
-
-    db.getDbClient().userDao().insert(dbSession, newUserDto()
-      .setLogin("login")
+    db.users().insertUser(u -> u.setLogin("login")
       .setName("Login")
       .setEmail("login@login.com")
-      .setActive(true)
-      );
-    dbSession.commit();
+      .setActive(true));
   }
 
   @Test
index 64a48357f2e2d414645e2f3847b0c1fb2e0d45cb..f1d0adf3e03f41b80d36fb8ab69af7da2bba5c9e 100644 (file)
@@ -36,8 +36,6 @@ import org.sonar.db.measure.custom.CustomMeasureDto;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.metric.MetricTesting;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.ServerException;
@@ -51,7 +49,6 @@ import static org.assertj.core.data.Offset.offset;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.sonar.db.measure.custom.CustomMeasureTesting.newCustomMeasureDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
 import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_DESCRIPTION;
 import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_ID;
 import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_VALUE;
@@ -78,8 +75,7 @@ public class UpdateActionTest {
 
     ws = new WsTester(new CustomMeasuresWs(new UpdateAction(dbClient, userSessionRule, system, validator, new CustomMeasureJsonWriter(new UserJsonWriter(userSessionRule)))));
 
-    db.getDbClient().userDao().insert(dbSession, newUserDto()
-      .setLogin("login")
+    db.users().insertUser(u -> u.setLogin("login")
       .setName("Login")
       .setEmail("login@login.com")
       .setActive(true)
index c996eaaf8e8cd9fa90425e23685ab3f0db056632..08e568b0f998c22d7fa86a647a3420a038110519 100644 (file)
@@ -55,6 +55,7 @@ import org.sonar.server.organization.TestOrganizationFlags;
 import org.sonar.server.qualityprofile.BuiltInQProfileRepository;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.usergroups.DefaultGroupCreatorImpl;
 import org.sonar.server.ws.TestRequest;
@@ -65,12 +66,16 @@ import org.sonarqube.ws.Organizations.Organization;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.mockito.Mockito.mock;
 import static org.sonar.core.config.CorePropertyDefinitions.ORGANIZATIONS_ANYONE_CAN_CREATE;
 import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_KEY;
 import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_NAME;
 import static org.sonar.server.organization.ws.OrganizationsWsTestSupport.STRING_257_CHARS_LONG;
 import static org.sonar.server.organization.ws.OrganizationsWsTestSupport.STRING_65_CHARS_LONG;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ORGANIZATION_UUIDS;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID;
 import static org.sonar.test.JsonAssert.assertJson;
 
 public class CreateActionTest {
@@ -362,7 +367,7 @@ public class CreateActionTest {
   }
 
   @Test
-  public void request_set_user_as_member_of_organization() {
+  public void set_user_as_member_of_organization() {
     UserDto user = dbTester.users().insertUser();
     userSession.logIn(user).setSystemAdministrator();
     dbTester.qualityGates().insertBuiltInQualityGate();
@@ -371,7 +376,10 @@ public class CreateActionTest {
 
     OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, "bar").get();
     assertThat(dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), user.getId())).isPresent();
-    assertThat(userIndex.getNullableByLogin(user.getLogin()).organizationUuids()).contains(organization.getUuid());
+    assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+      .setQuery(boolQuery()
+        .must(termQuery(FIELD_ORGANIZATION_UUIDS, organization.getUuid()))
+        .must(termQuery(FIELD_UUID, user.getUuid()))).get().getHits().getHits()).hasSize(1);
   }
 
   @Test
@@ -603,7 +611,7 @@ public class CreateActionTest {
   }
 
   private static void populateRequest(@Nullable String name, @Nullable String key, @Nullable String description, @Nullable String url, @Nullable String avatar,
-                                      TestRequest request) {
+    TestRequest request) {
     OrganizationsWsTestSupport.setParam(request, "name", name);
     OrganizationsWsTestSupport.setParam(request, "key", key);
     OrganizationsWsTestSupport.setParam(request, "description", description);
index 31f2e7d04b414476c125c83d33eecccd2fb258f2..921b65e56641fd214fc9bdac6f5a1c5a6261cbab 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.organization.ws;
 
 import java.util.HashSet;
 import javax.annotation.Nullable;
+import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -47,6 +48,7 @@ import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.user.index.UserQuery;
 import org.sonar.server.ws.TestRequest;
@@ -57,6 +59,8 @@ import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
 import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.groups.Tuple.tuple;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE;
 import static org.sonar.api.web.UserRole.CODEVIEWER;
 import static org.sonar.api.web.UserRole.USER;
@@ -65,6 +69,8 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_GATES;
 import static org.sonar.db.permission.OrganizationPermission.SCAN;
 import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ORGANIZATION_UUIDS;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID;
 
 public class RemoveMemberActionTest {
   @Rule
@@ -367,12 +373,30 @@ public class RemoveMemberActionTest {
 
   private void assertNotAMember(String organizationUuid, UserDto user) {
     assertThat(dbClient.organizationMemberDao().select(dbSession, organizationUuid, user.getId())).isNotPresent();
-    assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(organizationUuid).setTextQuery(user.getLogin()).build(), new SearchOptions()).getDocs()).isEmpty();
+    assertMemberInIndex(organizationUuid, user, false);
   }
 
   private void assertMember(String organizationUuid, UserDto user) {
     assertThat(dbClient.organizationMemberDao().select(dbSession, organizationUuid, user.getId())).isPresent();
-    assertThat(userIndex.getNullableByLogin(user.getLogin()).organizationUuids()).contains(organizationUuid);
+    assertThat(userIndex.search(UserQuery.builder()
+      .setOrganizationUuid(organizationUuid)
+      .setTextQuery(user.getLogin())
+      .build(),
+      new SearchOptions()).getDocs())
+        .hasSize(1);
+    assertMemberInIndex(organizationUuid, user, true);
+  }
+
+  private void assertMemberInIndex(String organizationUuid, UserDto user, boolean isMember) {
+    SearchRequestBuilder request = es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+      .setQuery(boolQuery()
+        .must(termQuery(FIELD_ORGANIZATION_UUIDS, organizationUuid))
+        .must(termQuery(FIELD_UUID, user.getUuid())));
+    if (isMember) {
+      assertThat(request.get().getHits().getHits()).hasSize(1);
+    } else {
+      assertThat(request.get().getHits().getHits()).isEmpty();
+    }
   }
 
   private void assertOrgPermissionsOfUser(UserDto user, OrganizationDto organization, OrganizationPermission... permissions) {
index f38fa07d8d424849680e64d16547a2c241413bc1..ac9fd1d769ac3c8453688c0f384b2c2ded4f2e51 100644 (file)
@@ -26,58 +26,41 @@ import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.UserDto;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertThat;
-import static org.sonar.db.DbTester.create;
+import static org.assertj.core.api.Java6Assertions.assertThat;
 
 public class DeprecatedUserFinderTest {
 
   @Rule
-  public DbTester dbTester = create(System2.INSTANCE);
-  private DeprecatedUserFinder underTest = new DeprecatedUserFinder(dbTester.getDbClient());
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private DeprecatedUserFinder underTest = new DeprecatedUserFinder(db.getDbClient());
 
   @Test
   public void shouldFindUserByLogin() {
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
 
-    UserDto simon = dbTester.users().insertUser(u -> u.setLogin("simon").setName("Simon Brandhof").setEmail("simon.brandhof@sonarsource.com"));
-    UserDto evgeny = dbTester.users().insertUser(u -> u.setLogin("godin").setName("Evgeny Mandrikov").setEmail("evgeny.mandrikov@sonarsource.com"));
-
-    User user = underTest.findByLogin(simon.getLogin());
-    assertThat(user.getId(), is(simon.getId()));
-    assertThat(user.getLogin(), is("simon"));
-    assertThat(user.getName(), is("Simon Brandhof"));
-    assertThat(user.getEmail(), is("simon.brandhof@sonarsource.com"));
+    User user = underTest.findByLogin(user1.getLogin());
+    assertThat(user.getId()).isEqualTo(user1.getId());
+    assertThat(user.getLogin()).isEqualTo(user1.getLogin());
+    assertThat(user.getName()).isEqualTo(user1.getName());
+    assertThat(user.getEmail()).isEqualTo(user1.getEmail());
 
-    user = underTest.findByLogin(evgeny.getLogin());
-    assertThat(user.getId(), is(evgeny.getId()));
-    assertThat(user.getLogin(), is("godin"));
-    assertThat(user.getName(), is("Evgeny Mandrikov"));
-    assertThat(user.getEmail(), is("evgeny.mandrikov@sonarsource.com"));
-
-    user = underTest.findByLogin("user");
-    assertThat(user, nullValue());
+    assertThat(underTest.findByLogin("unknown")).isNull();
   }
 
   @Test
   public void shouldFindUserById() {
-    UserDto simon = dbTester.users().insertUser(u -> u.setLogin("simon").setName("Simon Brandhof").setEmail("simon.brandhof@sonarsource.com"));
-    UserDto evgeny = dbTester.users().insertUser(u -> u.setLogin("godin").setName("Evgeny Mandrikov").setEmail("evgeny.mandrikov@sonarsource.com"));
-
-    User user = underTest.findById(simon.getId());
-    assertThat(user.getId(), is(simon.getId()));
-    assertThat(user.getLogin(), is("simon"));
-    assertThat(user.getName(), is("Simon Brandhof"));
-    assertThat(user.getEmail(), is("simon.brandhof@sonarsource.com"));
+    UserDto user1 = db.users().insertUser();
+    UserDto user2 = db.users().insertUser();
 
-    user = underTest.findById(evgeny.getId());
-    assertThat(user.getId(), is(evgeny.getId()));
-    assertThat(user.getLogin(), is("godin"));
-    assertThat(user.getName(), is("Evgeny Mandrikov"));
-    assertThat(user.getEmail(), is("evgeny.mandrikov@sonarsource.com"));
+    User user = underTest.findById(user1.getId());
+    assertThat(user.getId()).isEqualTo(user1.getId());
+    assertThat(user.getLogin()).isEqualTo(user1.getLogin());
+    assertThat(user.getName()).isEqualTo(user1.getName());
+    assertThat(user.getEmail()).isEqualTo(user1.getEmail());
 
-    user = underTest.findById(999);
-    assertThat(user, nullValue());
+    assertThat(underTest.findById(321)).isNull();
   }
 
 }
index 4c71fe74c63c7507d84d34b53268dada19adad88..56a805b5f4512d08f843606b51526134deafd700 100644 (file)
@@ -32,9 +32,18 @@ public class ExternalIdentityTest {
 
   @Test
   public void create_external_identity() {
-    ExternalIdentity externalIdentity = new ExternalIdentity("github", "login");
-    assertThat(externalIdentity.getId()).isEqualTo("login");
+    ExternalIdentity externalIdentity = new ExternalIdentity("github", "login", "ABCD");
+    assertThat(externalIdentity.getLogin()).isEqualTo("login");
+    assertThat(externalIdentity.getProvider()).isEqualTo("github");
+    assertThat(externalIdentity.getId()).isEqualTo("ABCD");
+  }
+
+  @Test
+  public void login_is_used_when_id_is_not_provided() {
+    ExternalIdentity externalIdentity = new ExternalIdentity("github", "login", null);
+    assertThat(externalIdentity.getLogin()).isEqualTo("login");
     assertThat(externalIdentity.getProvider()).isEqualTo("github");
+    assertThat(externalIdentity.getId()).isEqualTo("login");
   }
 
   @Test
@@ -42,15 +51,15 @@ public class ExternalIdentityTest {
     thrown.expect(NullPointerException.class);
     thrown.expectMessage("Identity provider cannot be null");
 
-    new ExternalIdentity(null, "login");
+    new ExternalIdentity(null, "login", "ABCD");
   }
 
   @Test
-  public void fail_with_NPE_when_identity_id_is_null() {
+  public void fail_with_NPE_when_identity_login_is_null() {
     thrown.expect(NullPointerException.class);
-    thrown.expectMessage("Identity id cannot be null");
+    thrown.expectMessage("Identity login cannot be null");
 
-    new ExternalIdentity("github", null);
+    new ExternalIdentity("github", null, "ABCD");
   }
 
 }
index 8667d312b7d60bcf4fe5b3fc055894da57d9fac3..260eb4643178b73b0ba4266aa6a187497cc3e39c 100644 (file)
@@ -66,11 +66,12 @@ public class NewUserTest {
       .setLogin("login")
       .setName("name")
       .setEmail("email")
-      .setExternalIdentity(new ExternalIdentity("github", "github_login"))
+      .setExternalIdentity(new ExternalIdentity("github", "github_login", "ABCD"))
       .build();
 
     assertThat(newUser.externalIdentity().getProvider()).isEqualTo("github");
-    assertThat(newUser.externalIdentity().getId()).isEqualTo("github_login");
+    assertThat(newUser.externalIdentity().getLogin()).isEqualTo("github_login");
+    assertThat(newUser.externalIdentity().getId()).isEqualTo("ABCD");
   }
 
   @Test
@@ -83,7 +84,7 @@ public class NewUserTest {
       .setName("name")
       .setEmail("email")
       .setPassword("password")
-      .setExternalIdentity(new ExternalIdentity("github", "github_login"))
+      .setExternalIdentity(new ExternalIdentity("github", "github_login", "ABCD"))
       .build();
   }
 }
index 56e470ef569a530aeda1aceb4d117b3820c3c658..cbe9fe6c2a9059b80f4619f7a6ba9282cefd2da3 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.user;
 
 import java.util.Arrays;
 import javax.annotation.Nullable;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -30,9 +29,7 @@ import org.sonar.api.web.UserRole;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.OrganizationPermission;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -45,27 +42,12 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.core.permission.GlobalPermissions.PROVISIONING;
 import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
 import static org.sonar.db.component.ComponentTesting.newChildComponent;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
 import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
 import static org.sonar.db.permission.OrganizationPermission.SCAN;
 
 public class ServerUserSessionTest {
-  private static final String LOGIN = "marius";
-
-  private static final String PUBLIC_PROJECT_UUID = "public_project";
-  private static final String PRIVATE_PROJECT_UUID = "private_project";
-  private static final String FILE_KEY = "com.foo:Bar:BarFile.xoo";
-  private static final String FILE_UUID = "BCDE";
-  private static final UserDto ROOT_USER_DTO = new UserDto() {
-    {
-      setRoot(true);
-    }
-  }.setLogin("root_user");
-  private static final UserDto NON_ROOT_USER_DTO = new UserDto() {
-    {
-      setRoot(false);
-    }
-  }.setLogin("regular_user");
 
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
@@ -73,23 +55,8 @@ public class ServerUserSessionTest {
   public ExpectedException expectedException = ExpectedException.none();
 
   private DbClient dbClient = db.getDbClient();
-  private UserDto user;
-  private GroupDto groupOfUser;
   private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
   private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
-  private OrganizationDto organization;
-  private ComponentDto publicProject;
-  private ComponentDto privateProject;
-
-  @Before
-  public void setUp() throws Exception {
-    organization = db.organizations().insert();
-    publicProject = db.components().insertPublicProject(organization, PUBLIC_PROJECT_UUID);
-    privateProject = db.components().insertPrivateProject(organization, dto -> dto.setUuid(PRIVATE_PROJECT_UUID).setProjectUuid(PRIVATE_PROJECT_UUID).setPrivate(true));
-    db.components().insertComponent(ComponentTesting.newFileDto(publicProject, null, FILE_UUID).setDbKey(FILE_KEY));
-    user = db.users().insertUser(LOGIN);
-    groupOfUser = db.users().insertGroup(organization);
-  }
 
   @Test
   public void anonymous_is_not_logged_in_and_does_not_have_login() {
@@ -106,11 +73,13 @@ public class ServerUserSessionTest {
 
   @Test
   public void getGroups_is_empty_if_user_is_not_member_of_any_group() {
+    UserDto user = db.users().insertUser();
     assertThat(newUserSession(user).getGroups()).isEmpty();
   }
 
   @Test
   public void getGroups_returns_the_groups_of_logged_in_user() {
+    UserDto user = db.users().insertUser();
     GroupDto group1 = db.users().insertGroup();
     GroupDto group2 = db.users().insertGroup();
     db.users().insertMember(group1, user);
@@ -121,6 +90,7 @@ public class ServerUserSessionTest {
 
   @Test
   public void getGroups_keeps_groups_in_cache() {
+    UserDto user = db.users().insertUser();
     GroupDto group1 = db.users().insertGroup();
     GroupDto group2 = db.users().insertGroup();
     db.users().insertMember(group1, user);
@@ -135,13 +105,18 @@ public class ServerUserSessionTest {
 
   @Test
   public void isRoot_is_false_is_flag_root_is_false_on_UserDto() {
-    assertThat(newUserSession(ROOT_USER_DTO).isRoot()).isTrue();
-    assertThat(newUserSession(NON_ROOT_USER_DTO).isRoot()).isFalse();
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+    assertThat(newUserSession(root).isRoot()).isTrue();
+
+    UserDto notRoot = db.users().insertUser();
+    assertThat(newUserSession(notRoot).isRoot()).isFalse();
   }
 
   @Test
   public void checkIsRoot_throws_IPFE_if_flag_root_is_false_on_UserDto() {
-    UserSession underTest = newUserSession(NON_ROOT_USER_DTO);
+    UserDto user = db.users().insertUser();
+    UserSession underTest = newUserSession(user);
 
     expectInsufficientPrivilegesForbiddenException();
 
@@ -150,32 +125,49 @@ public class ServerUserSessionTest {
 
   @Test
   public void checkIsRoot_does_not_fail_if_flag_root_is_true_on_UserDto() {
-    UserSession underTest = newUserSession(ROOT_USER_DTO);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+
+    UserSession underTest = newUserSession(root);
 
     assertThat(underTest.checkIsRoot()).isSameAs(underTest);
   }
 
   @Test
   public void hasComponentUuidPermission_returns_true_when_flag_root_is_true_on_UserDto_no_matter_if_user_has_project_permission_for_given_uuid() {
-    UserSession underTest = newUserSession(ROOT_USER_DTO);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+
+    UserSession underTest = newUserSession(root);
 
-    assertThat(underTest.hasComponentUuidPermission(UserRole.USER, FILE_UUID)).isTrue();
-    assertThat(underTest.hasComponentUuidPermission(UserRole.CODEVIEWER, FILE_UUID)).isTrue();
-    assertThat(underTest.hasComponentUuidPermission(UserRole.ADMIN, FILE_UUID)).isTrue();
+    assertThat(underTest.hasComponentUuidPermission(UserRole.USER, file.uuid())).isTrue();
+    assertThat(underTest.hasComponentUuidPermission(UserRole.CODEVIEWER, file.uuid())).isTrue();
+    assertThat(underTest.hasComponentUuidPermission(UserRole.ADMIN, file.uuid())).isTrue();
     assertThat(underTest.hasComponentUuidPermission("whatever", "who cares?")).isTrue();
   }
 
   @Test
   public void checkComponentUuidPermission_succeeds_if_user_has_permission_for_specified_uuid_in_db() {
-    UserSession underTest = newUserSession(ROOT_USER_DTO);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto project = db.components().insertPrivateProject(organization);
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
 
-    assertThat(underTest.checkComponentUuidPermission(UserRole.USER, FILE_UUID)).isSameAs(underTest);
+    UserSession underTest = newUserSession(root);
+
+    assertThat(underTest.checkComponentUuidPermission(UserRole.USER, file.uuid())).isSameAs(underTest);
     assertThat(underTest.checkComponentUuidPermission("whatever", "who cares?")).isSameAs(underTest);
   }
 
   @Test
   public void checkComponentUuidPermission_fails_with_FE_when_user_has_not_permission_for_specified_uuid_in_db() {
-    addProjectPermissions(privateProject, UserRole.USER);
+    UserDto user = db.users().insertUser();
+    ComponentDto project = db.components().insertPrivateProject();
+    db.users().insertProjectPermissionOnUser(user, UserRole.USER, project);
     UserSession session = newUserSession(user);
 
     expectInsufficientPrivilegesForbiddenException();
@@ -186,33 +178,37 @@ public class ServerUserSessionTest {
   @Test
   public void checkPermission_throws_ForbiddenException_when_user_doesnt_have_the_specified_permission_on_organization() {
     OrganizationDto org = db.organizations().insert();
-    db.users().insertUser(NON_ROOT_USER_DTO);
+    UserDto user = db.users().insertUser();
 
     expectInsufficientPrivilegesForbiddenException();
 
-    newUserSession(NON_ROOT_USER_DTO).checkPermission(PROVISION_PROJECTS, org);
+    newUserSession(user).checkPermission(PROVISION_PROJECTS, org);
   }
 
   @Test
   public void checkPermission_succeeds_when_user_has_the_specified_permission_on_organization() {
     OrganizationDto org = db.organizations().insert();
-    db.users().insertUser(NON_ROOT_USER_DTO);
-    db.users().insertPermissionOnUser(org, NON_ROOT_USER_DTO, PROVISIONING);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+    db.users().insertPermissionOnUser(org, root, PROVISIONING);
 
-    newUserSession(NON_ROOT_USER_DTO).checkPermission(PROVISION_PROJECTS, org);
+    newUserSession(root).checkPermission(PROVISION_PROJECTS, org);
   }
 
   @Test
   public void checkPermission_succeeds_when_user_is_root() {
     OrganizationDto org = db.organizations().insert();
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
 
-    newUserSession(ROOT_USER_DTO).checkPermission(PROVISION_PROJECTS, org);
+    newUserSession(root).checkPermission(PROVISION_PROJECTS, org);
   }
 
   @Test
   public void test_hasPermission_on_organization_for_logged_in_user() {
     OrganizationDto org = db.organizations().insert();
     ComponentDto project = db.components().insertPrivateProject(org);
+    UserDto user = db.users().insertUser();
     db.users().insertPermissionOnUser(org, user, PROVISION_PROJECTS);
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, project);
 
@@ -236,6 +232,7 @@ public class ServerUserSessionTest {
   @Test
   public void hasPermission_on_organization_keeps_cache_of_permissions_of_logged_in_user() {
     OrganizationDto org = db.organizations().insert();
+    UserDto user = db.users().insertUser();
     db.users().insertPermissionOnUser(org, user, PROVISIONING);
 
     UserSession session = newUserSession(user);
@@ -269,6 +266,9 @@ public class ServerUserSessionTest {
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_without_permissions() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+
     ServerUserSession underTest = newAnonymousSession();
 
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue();
@@ -277,33 +277,45 @@ public class ServerUserSessionTest {
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_with_global_permissions() {
-    ServerUserSession underTest = newAnonymousSession();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnAnyone("p1", publicProject);
 
+    ServerUserSession underTest = newAnonymousSession();
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_with_group_permissions() {
-    ServerUserSession underTest = newAnonymousSession();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnGroup(db.users().insertGroup(organization), "p1", publicProject);
 
+    ServerUserSession underTest = newAnonymousSession();
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_permissions_USER_and_CODEVIEWER_on_public_projects_with_user_permissions() {
-    ServerUserSession underTest = newAnonymousSession();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnUser(db.users().insertUser(), "p1", publicProject);
 
+    ServerUserSession underTest = newAnonymousSession();
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, publicProject)).isTrue();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, publicProject)).isTrue();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_authenticated_user_for_permissions_USER_and_CODEVIEWER_on_private_projects_without_permissions() {
+    UserDto user = db.users().insertUser();
+    ComponentDto privateProject = db.components().insertPrivateProject();
+
     ServerUserSession underTest = newUserSession(user);
 
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse();
@@ -312,71 +324,104 @@ public class ServerUserSessionTest {
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_authenticated_user_for_permissions_USER_and_CODEVIEWER_on_private_projects_with_group_permissions() {
-    ServerUserSession underTest = newUserSession(user);
+    UserDto user = db.users().insertUser();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto privateProject = db.components().insertPrivateProject(organization);
     db.users().insertProjectPermissionOnGroup(db.users().insertGroup(organization), "p1", privateProject);
 
+    ServerUserSession underTest = newUserSession(user);
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_authenticated_user_for_permissions_USER_and_CODEVIEWER_on_private_projects_with_user_permissions() {
-    ServerUserSession underTest = newUserSession(user);
+    UserDto user = db.users().insertUser();
+    ComponentDto privateProject = db.components().insertPrivateProject();
     db.users().insertProjectPermissionOnUser(db.users().insertUser(), "p1", privateProject);
 
+    ServerUserSession underTest = newUserSession(user);
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.USER, privateProject)).isFalse();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, UserRole.CODEVIEWER, privateProject)).isFalse();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_true_for_anonymous_user_for_inserted_permissions_on_group_AnyOne_on_public_projects() {
-    ServerUserSession underTest = newAnonymousSession();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnAnyone("p1", publicProject);
 
+    ServerUserSession underTest = newAnonymousSession();
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", publicProject)).isTrue();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_group_on_public_projects() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+    GroupDto group = db.users().insertGroup(organization);
+    db.users().insertProjectPermissionOnGroup(group, "p1", publicProject);
+
     ServerUserSession underTest = newAnonymousSession();
-    db.users().insertProjectPermissionOnGroup(groupOfUser, "p1", publicProject);
 
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", publicProject)).isFalse();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_group_on_private_projects() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto privateProject = db.components().insertPrivateProject(organization);
+    GroupDto group = db.users().insertGroup(organization);
+    db.users().insertProjectPermissionOnGroup(group, "p1", privateProject);
+
     ServerUserSession underTest = newAnonymousSession();
-    db.users().insertProjectPermissionOnGroup(groupOfUser, "p1", privateProject);
 
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", privateProject)).isFalse();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_user_on_public_projects() {
-    ServerUserSession underTest = newAnonymousSession();
+    UserDto user = db.users().insertUser();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnUser(user, "p1", publicProject);
 
+    ServerUserSession underTest = newAnonymousSession();
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", publicProject)).isFalse();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_false_for_anonymous_user_for_inserted_permissions_on_user_on_private_projects() {
+    UserDto user = db.users().insertUser();
+    ComponentDto project = db.components().insertPrivateProject();
+    db.users().insertProjectPermissionOnUser(user, "p1", project);
+
     ServerUserSession underTest = newAnonymousSession();
-    db.users().insertProjectPermissionOnUser(user, "p1", privateProject);
 
-    assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", privateProject)).isFalse();
+    assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", project)).isFalse();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_returns_true_for_any_project_or_permission_for_root_user() {
-    ServerUserSession underTest = newUserSession(ROOT_USER_DTO);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+
+    ServerUserSession underTest = newUserSession(root);
 
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "does not matter", publicProject)).isTrue();
   }
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_keeps_cache_of_permissions_of_logged_in_user() {
+    UserDto user = db.users().insertUser();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, publicProject);
 
     UserSession underTest = newUserSession(user);
@@ -393,6 +438,8 @@ public class ServerUserSessionTest {
 
   @Test
   public void hasComponentPermissionByDtoOrUuid_keeps_cache_of_permissions_of_anonymous_user() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
     db.users().insertProjectPermissionOnAnyone(UserRole.ADMIN, publicProject);
 
     UserSession underTest = newAnonymousSession();
@@ -416,6 +463,10 @@ public class ServerUserSessionTest {
 
   @Test
   public void keepAuthorizedComponents_returns_empty_list_if_no_permissions_are_granted() {
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+    ComponentDto privateProject = db.components().insertPrivateProject(organization);
+
     UserSession underTest = newAnonymousSession();
 
     assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty();
@@ -423,26 +474,40 @@ public class ServerUserSessionTest {
 
   @Test
   public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_logged_in_user() {
-    UserSession underTest = newUserSession(user);
+    UserDto user = db.users().insertUser();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+    ComponentDto privateProject = db.components().insertPrivateProject(organization);
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, privateProject);
 
+    UserSession underTest = newUserSession(user);
+
     assertThat(underTest.keepAuthorizedComponents(UserRole.ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty();
     assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(privateProject);
   }
 
   @Test
   public void keepAuthorizedComponents_filters_components_with_granted_permissions_for_anonymous() {
-    UserSession underTest = newAnonymousSession();
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+    ComponentDto privateProject = db.components().insertPrivateProject(organization);
     db.users().insertProjectPermissionOnAnyone(UserRole.ISSUE_ADMIN, publicProject);
 
+    UserSession underTest = newAnonymousSession();
+
     assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject))).isEmpty();
     assertThat(underTest.keepAuthorizedComponents(UserRole.ISSUE_ADMIN, Arrays.asList(privateProject, publicProject))).containsExactly(publicProject);
   }
 
   @Test
   public void keepAuthorizedComponents_returns_all_specified_components_if_root() {
-    user = db.users().makeRoot(user);
-    UserSession underTest = newUserSession(user);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+    OrganizationDto organization = db.organizations().insert();
+    ComponentDto publicProject = db.components().insertPublicProject(organization);
+    ComponentDto privateProject = db.components().insertPrivateProject(organization);
+
+    UserSession underTest = newUserSession(root);
 
     assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, Arrays.asList(privateProject, publicProject)))
       .containsExactly(privateProject, publicProject);
@@ -450,9 +515,11 @@ public class ServerUserSessionTest {
 
   @Test
   public void keepAuthorizedComponents_on_branches() {
-    user = db.users().insertUser();
+    UserDto user = db.users().insertUser();
+    ComponentDto privateProject = db.components().insertPrivateProject();
     db.users().insertProjectPermissionOnUser(user, UserRole.ADMIN, privateProject);
     ComponentDto privateBranchProject = db.components().insertProjectBranch(privateProject);
+
     UserSession underTest = newUserSession(user);
 
     assertThat(underTest.keepAuthorizedComponents(UserRole.ADMIN, asList(privateProject, privateBranchProject)))
@@ -462,8 +529,10 @@ public class ServerUserSessionTest {
   @Test
   public void isSystemAdministrator_returns_true_if_org_feature_is_enabled_and_user_is_root() {
     organizationFlags.setEnabled(true);
-    user = db.users().makeRoot(user);
-    UserSession session = newUserSession(user);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+
+    UserSession session = newUserSession(root);
 
     assertThat(session.isSystemAdministrator()).isTrue();
   }
@@ -471,7 +540,8 @@ public class ServerUserSessionTest {
   @Test
   public void isSystemAdministrator_returns_false_if_org_feature_is_enabled_and_user_is_not_root() {
     organizationFlags.setEnabled(true);
-    user = db.users().makeNotRoot(user);
+    UserDto user = db.users().insertUser();
+
     UserSession session = newUserSession(user);
 
     assertThat(session.isSystemAdministrator()).isFalse();
@@ -480,8 +550,9 @@ public class ServerUserSessionTest {
   @Test
   public void isSystemAdministrator_returns_false_if_org_feature_is_enabled_and_user_is_administrator_of_default_organization() {
     organizationFlags.setEnabled(true);
-    user = db.users().makeNotRoot(user);
+    UserDto user = db.users().insertUser();
     db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN);
+
     UserSession session = newUserSession(user);
 
     assertThat(session.isSystemAdministrator()).isFalse();
@@ -490,8 +561,9 @@ public class ServerUserSessionTest {
   @Test
   public void isSystemAdministrator_returns_true_if_org_feature_is_disabled_and_user_is_administrator_of_default_organization() {
     organizationFlags.setEnabled(false);
-    user = db.users().makeNotRoot(user);
+    UserDto user = db.users().insertUser();
     db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN);
+
     UserSession session = newUserSession(user);
 
     assertThat(session.isSystemAdministrator()).isTrue();
@@ -500,8 +572,9 @@ public class ServerUserSessionTest {
   @Test
   public void isSystemAdministrator_returns_false_if_org_feature_is_disabled_and_user_is_not_administrator_of_default_organization() {
     organizationFlags.setEnabled(true);
-    user = db.users().makeNotRoot(user);
+    UserDto user = db.users().insertUser();
     db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, PROVISIONING);
+
     UserSession session = newUserSession(user);
 
     assertThat(session.isSystemAdministrator()).isFalse();
@@ -510,8 +583,9 @@ public class ServerUserSessionTest {
   @Test
   public void keep_isSystemAdministrator_flag_in_cache() {
     organizationFlags.setEnabled(false);
-    user = db.users().makeNotRoot(user);
+    UserDto user = db.users().insertUser();
     db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN);
+
     UserSession session = newUserSession(user);
 
     session.checkIsSystemAdministrator();
@@ -526,8 +600,10 @@ public class ServerUserSessionTest {
   @Test
   public void checkIsSystemAdministrator_succeeds_if_system_administrator() {
     organizationFlags.setEnabled(true);
-    user = db.users().makeRoot(user);
-    UserSession session = newUserSession(user);
+    UserDto root = db.users().insertUser();
+    root = db.users().makeRoot(root);
+
+    UserSession session = newUserSession(root);
 
     session.checkIsSystemAdministrator();
   }
@@ -535,7 +611,8 @@ public class ServerUserSessionTest {
   @Test
   public void checkIsSystemAdministrator_throws_ForbiddenException_if_not_system_administrator() {
     organizationFlags.setEnabled(true);
-    user = db.users().makeNotRoot(user);
+    UserDto user = db.users().insertUser();
+
     UserSession session = newUserSession(user);
 
     expectedException.expect(ForbiddenException.class);
@@ -546,6 +623,8 @@ public class ServerUserSessionTest {
 
   @Test
   public void hasComponentPermission_on_branch_checks_permissions_of_its_project() {
+    UserDto user = db.users().insertUser();
+    ComponentDto privateProject = db.components().insertPrivateProject();
     ComponentDto branch = db.components().insertProjectBranch(privateProject, b -> b.setKey("feature/foo"));
     ComponentDto fileInBranch = db.components().insertComponent(newChildComponent("fileUuid", branch, branch));
 
@@ -553,6 +632,7 @@ public class ServerUserSessionTest {
     db.users().insertProjectPermissionOnUser(user, "p1", privateProject);
 
     UserSession underTest = newUserSession(user);
+
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", privateProject)).isTrue();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", branch)).isTrue();
     assertThat(hasComponentPermissionByDtoOrUuid(underTest, "p1", fileInBranch)).isTrue();
@@ -566,20 +646,6 @@ public class ServerUserSessionTest {
     return newUserSession(null);
   }
 
-  private void addProjectPermissions(ComponentDto component, String... permissions) {
-    addPermissions(component, permissions);
-  }
-
-  private void addPermissions(@Nullable ComponentDto component, String... permissions) {
-    for (String permission : permissions) {
-      if (component == null) {
-        db.users().insertPermissionOnUser(user, OrganizationPermission.fromKey(permission));
-      } else {
-        db.users().insertProjectPermissionOnUser(user, permission, component);
-      }
-    }
-  }
-
   private void expectInsufficientPrivilegesForbiddenException() {
     expectedException.expect(ForbiddenException.class);
     expectedException.expectMessage("Insufficient privileges");
index dbc5fe59fb89149ae2d99fa0a130650f0d05e182..26e2835123c0ab8c1c4c93db95592ccd9ec6fb05 100644 (file)
@@ -36,7 +36,6 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.GroupTesting;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.authentication.LocalAuthentication;
 import org.sonar.server.authentication.LocalAuthentication.HashMethod;
@@ -60,7 +59,6 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.sonar.core.config.CorePropertyDefinitions.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS;
-import static org.sonar.db.user.UserTesting.newDisabledUser;
 import static org.sonar.db.user.UserTesting.newLocalUser;
 import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
 
@@ -103,7 +101,7 @@ public class UserUpdaterCreateTest {
       .setPassword("PASSWORD")
       .setScmAccounts(ImmutableList.of("u1", "u_1", "User 1"))
       .build(), u -> {
-    });
+      });
 
     assertThat(dto.getId()).isNotNull();
     assertThat(dto.getLogin()).isEqualTo("user");
@@ -138,7 +136,7 @@ public class UserUpdaterCreateTest {
       .setLogin("us")
       .setName("User")
       .build(), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, "us");
     assertThat(dto.getId()).isNotNull();
@@ -158,10 +156,10 @@ public class UserUpdaterCreateTest {
       .setName("User")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, "user");
-    assertThat(dto.getExternalIdentity()).isEqualTo("user");
+    assertThat(dto.getExternalLogin()).isEqualTo("user");
     assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube");
     assertThat(dto.isLocal()).isTrue();
   }
@@ -173,13 +171,14 @@ public class UserUpdaterCreateTest {
     underTest.createAndCommit(db.getSession(), NewUser.builder()
       .setLogin("user")
       .setName("User")
-      .setExternalIdentity(new ExternalIdentity("github", "github-user"))
+      .setExternalIdentity(new ExternalIdentity("github", "github-user", "ABCD"))
       .build(), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, "user");
     assertThat(dto.isLocal()).isFalse();
-    assertThat(dto.getExternalIdentity()).isEqualTo("github-user");
+    assertThat(dto.getExternalId()).isEqualTo("ABCD");
+    assertThat(dto.getExternalLogin()).isEqualTo("github-user");
     assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
     assertThat(dto.getCryptedPassword()).isNull();
     assertThat(dto.getSalt()).isNull();
@@ -192,13 +191,14 @@ public class UserUpdaterCreateTest {
     underTest.createAndCommit(db.getSession(), NewUser.builder()
       .setLogin("user")
       .setName("User")
-      .setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, "user"))
+      .setExternalIdentity(new ExternalIdentity(SQ_AUTHORITY, "user", null))
       .build(), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, "user");
     assertThat(dto.isLocal()).isFalse();
-    assertThat(dto.getExternalIdentity()).isEqualTo("user");
+    assertThat(dto.getExternalId()).isEqualTo("user");
+    assertThat(dto.getExternalLogin()).isEqualTo("user");
     assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube");
     assertThat(dto.getCryptedPassword()).isNull();
     assertThat(dto.getSalt()).isNull();
@@ -214,7 +214,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList("u1", "", null))
       .build(), u -> {
-    });
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, "user").getScmAccountsAsList()).containsOnly("u1");
   }
@@ -229,7 +229,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList(""))
       .build(), u -> {
-    });
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, "user").getScmAccounts()).isNull();
   }
@@ -244,7 +244,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList("u1", "u1"))
       .build(), u -> {
-    });
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, "user").getScmAccountsAsList()).containsOnly("u1");
   }
@@ -258,7 +258,7 @@ public class UserUpdaterCreateTest {
       .setLogin("user")
       .setName("User")
       .build(), u -> {
-    });
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, "user").isOnboarded()).isTrue();
   }
@@ -272,7 +272,7 @@ public class UserUpdaterCreateTest {
       .setLogin("user")
       .setName("User")
       .build(), u -> {
-    });
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, "user").isOnboarded()).isFalse();
   }
@@ -288,9 +288,9 @@ public class UserUpdaterCreateTest {
       .setEmail("user@mail.com")
       .setPassword("PASSWORD")
       .build(), u -> {
-    }, otherUser);
+      }, otherUser);
 
-    assertThat(es.getIds(UserIndexDefinition.INDEX_TYPE_USER)).containsExactlyInAnyOrder(created.getLogin(), otherUser.getLogin());
+    assertThat(es.getIds(UserIndexDefinition.INDEX_TYPE_USER)).containsExactlyInAnyOrder(created.getUuid(), otherUser.getUuid());
   }
 
   @Test
@@ -304,7 +304,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -318,7 +318,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -332,7 +332,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -346,7 +346,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -360,7 +360,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -374,7 +374,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -388,7 +388,7 @@ public class UserUpdaterCreateTest {
       .setEmail("marius@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -402,7 +402,7 @@ public class UserUpdaterCreateTest {
       .setEmail(Strings.repeat("m", 101))
       .setPassword("password")
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -414,7 +414,7 @@ public class UserUpdaterCreateTest {
         .setEmail("marius@mail.com")
         .setPassword("")
         .build(), u -> {
-      });
+        });
       fail();
     } catch (BadRequestException e) {
       assertThat(e.errors()).hasSize(3);
@@ -435,7 +435,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList("jo"))
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -453,7 +453,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList("john@email.com"))
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -468,7 +468,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password2")
       .setScmAccounts(asList(DEFAULT_LOGIN))
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -483,6 +483,38 @@ public class UserUpdaterCreateTest {
       .setPassword("password2")
       .setScmAccounts(asList("marius2@mail.com"))
       .build(), u -> {
+      });
+  }
+
+  @Test
+  public void fail_to_create_user_when_login_already_exists() {
+    createDefaultGroup();
+    UserDto existingUser = db.users().insertUser(u -> u.setLogin("existing_login"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A user with login 'existing_login' already exists");
+
+    underTest.createAndCommit(db.getSession(), NewUser.builder()
+      .setLogin(existingUser.getLogin())
+      .setName("User")
+      .setPassword("PASSWORD")
+      .build(), u -> {
+      });
+  }
+
+  @Test
+  public void fail_to_create_user_when_external_id_and_external_provider_already_exists() {
+    createDefaultGroup();
+    UserDto existingUser = db.users().insertUser(u -> u.setExternalId("existing_external_id").setExternalIdentityProvider("existing_external_provider"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A user with provider id 'existing_external_id' and identity provider 'existing_external_provider' already exists");
+
+    underTest.createAndCommit(db.getSession(), NewUser.builder()
+      .setLogin("new_login")
+      .setName("User")
+      .setExternalIdentity(new ExternalIdentity(existingUser.getExternalIdentityProvider(), existingUser.getExternalLogin(), existingUser.getExternalId()))
+      .build(), u -> {
     });
   }
 
@@ -497,7 +529,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList("u1", "u_1"))
       .build(), u -> {
-    });
+      });
 
     verify(newUserNotifier).onNewUser(newUserHandler.capture());
     assertThat(newUserHandler.getValue().getLogin()).isEqualTo("user");
@@ -516,7 +548,7 @@ public class UserUpdaterCreateTest {
       .setEmail("user@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
 
     Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList("user"));
     assertThat(groups.get("user")).containsOnly(defaultGroup.getName());
@@ -533,7 +565,7 @@ public class UserUpdaterCreateTest {
       .setEmail("user@mail.com")
       .setPassword("password")
       .build(), u -> {
-    });
+      });
 
     Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList("user"));
     assertThat(groups.get("user")).isEmpty();
@@ -551,7 +583,7 @@ public class UserUpdaterCreateTest {
       .setPassword("password")
       .setScmAccounts(asList("u1", "u_1"))
       .build(), u -> {
-    });
+      });
   }
 
   @Test
@@ -564,7 +596,7 @@ public class UserUpdaterCreateTest {
       .setEmail("user@mail.com")
       .setPassword("PASSWORD")
       .build(), u -> {
-    });
+      });
 
     verify(organizationCreation).createForUser(any(DbSession.class), eq(dto));
   }
@@ -580,7 +612,7 @@ public class UserUpdaterCreateTest {
       .setEmail("user@mail.com")
       .setPassword("PASSWORD")
       .build(), u -> {
-    });
+      });
 
     assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isPresent();
   }
@@ -596,230 +628,11 @@ public class UserUpdaterCreateTest {
       .setEmail("user@mail.com")
       .setPassword("PASSWORD")
       .build(), u -> {
-    });
-
-    assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isNotPresent();
-  }
-
-  @Test
-  public void reactivate_user_when_creating_user_with_existing_login() {
-    UserDto user = db.users().insertUser(newDisabledUser(DEFAULT_LOGIN)
-      .setLocal(false));
-    createDefaultGroup();
-
-    UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .build(), u -> {
-    });
-    session.commit();
-
-    assertThat(dto.isActive()).isTrue();
-    assertThat(dto.getName()).isEqualTo("Marius2");
-    assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
-    assertThat(dto.getScmAccounts()).isNull();
-    assertThat(dto.isLocal()).isTrue();
-
-    assertThat(dto.getSalt()).isNull();
-    assertThat(dto.getHashMethod()).isEqualTo(HashMethod.BCRYPT.name());
-    assertThat(dto.getCryptedPassword()).isNotNull().isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
-    assertThat(dto.getCreatedAt()).isEqualTo(user.getCreatedAt());
-    assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
-
-    assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN).isActive()).isTrue();
-  }
-
-  @Test
-  public void reactivate_user_not_having_password() {
-    UserDto user = db.users().insertUser(newDisabledUser("marius").setName("Marius").setEmail("marius@lesbronzes.fr")
-      .setSalt(null)
-      .setCryptedPassword(null));
-    createDefaultGroup();
-
-    UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .build(), u -> {
-    });
-    session.commit();
-
-    assertThat(dto.isActive()).isTrue();
-    assertThat(dto.getName()).isEqualTo("Marius2");
-    assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
-    assertThat(dto.getScmAccounts()).isNull();
-
-    assertThat(dto.getSalt()).isNull();
-    assertThat(dto.getCryptedPassword()).isNull();
-    assertThat(dto.getCreatedAt()).isEqualTo(user.getCreatedAt());
-    assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
-  }
-
-  @Test
-  public void reactivate_user_with_external_provider() {
-    db.users().insertUser(newDisabledUser(DEFAULT_LOGIN)
-      .setLocal(true));
-    createDefaultGroup();
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setExternalIdentity(new ExternalIdentity("github", "john"))
-      .build(), u -> {
-    });
-    session.commit();
-
-    UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
-    assertThat(dto.isLocal()).isFalse();
-    assertThat(dto.getExternalIdentity()).isEqualTo("john");
-    assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
-  }
-
-  @Test
-  public void reactivate_user_with_local_provider() {
-    db.users().insertUser(newDisabledUser(DEFAULT_LOGIN)
-      .setLocal(true));
-    createDefaultGroup();
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setPassword("password")
-      .build(), u -> {
-    });
-    session.commit();
-
-    UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
-    assertThat(dto.isLocal()).isTrue();
-    assertThat(dto.getExternalIdentity()).isEqualTo(DEFAULT_LOGIN);
-    assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube");
-  }
-
-  @Test
-  public void fail_to_reactivate_user_if_not_disabled() {
-    db.users().insertUser(newLocalUser("marius", "Marius", "marius@lesbronzes.fr"));
-    createDefaultGroup();
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("An active user with login 'marius' already exists");
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .build(), u -> {
-    });
-  }
-
-  @Test
-  public void associate_default_groups_when_reactivating_user_and_organizations_are_disabled() {
-    organizationFlags.setEnabled(false);
-    UserDto userDto = db.users().insertUser(newDisabledUser(DEFAULT_LOGIN)
-      .setLocal(true));
-    db.organizations().insertForUuid("org1");
-    GroupDto groupDto = db.users().insertGroup(GroupTesting.newGroupDto().setName("sonar-devs").setOrganizationUuid("org1"));
-    db.users().insertMember(groupDto, userDto);
-    GroupDto defaultGroup = createDefaultGroup();
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .build(), u -> {
-    });
-    session.commit();
-
-    Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN));
-    assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isTrue();
-  }
-
-  @Test
-  public void does_not_associate_default_groups_when_reactivating_user_and_organizations_are_enabled() {
-    organizationFlags.setEnabled(true);
-    UserDto userDto = db.users().insertUser(newDisabledUser(DEFAULT_LOGIN)
-      .setLocal(true));
-    db.organizations().insertForUuid("org1");
-    GroupDto groupDto = db.users().insertGroup(GroupTesting.newGroupDto().setName("sonar-devs").setOrganizationUuid("org1"));
-    db.users().insertMember(groupDto, userDto);
-    GroupDto defaultGroup = createDefaultGroup();
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .build(), u -> {
-    });
-    session.commit();
-
-    Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN));
-    assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isFalse();
-  }
-
-  @Test
-  public void add_user_as_member_of_default_organization_when_reactivating_user_and_organizations_are_disabled() {
-    organizationFlags.setEnabled(false);
-    db.users().insertUser(newDisabledUser(DEFAULT_LOGIN));
-    createDefaultGroup();
-
-    UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder().setLogin(DEFAULT_LOGIN).setName("Name").build(), u -> {
-    });
-    session.commit();
-
-    assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isPresent();
-  }
-
-  @Test
-  public void does_not_add_user_as_member_of_default_organization_when_reactivating_user_and_organizations_are_enabled() {
-    organizationFlags.setEnabled(true);
-    db.users().insertUser(newDisabledUser(DEFAULT_LOGIN));
-    createDefaultGroup();
-
-    UserDto dto = underTest.createAndCommit(db.getSession(), NewUser.builder().setLogin(DEFAULT_LOGIN).setName("Name").build(), u -> {
-    });
-    session.commit();
+      });
 
     assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isNotPresent();
   }
 
-  @Test
-  public void reactivate_not_onboarded_user_if_onboarding_setting_is_set_to_false() {
-    settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, false);
-    UserDto user = db.users().insertUser(u -> u
-      .setActive(false)
-      .setOnboarded(false));
-    createDefaultGroup();
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(user.getLogin())
-      .setName("name")
-      .build(), u -> {
-    });
-
-    assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isTrue();
-  }
-
-  @Test
-  public void reactivate_onboarded_user_if_onboarding_setting_is_set_to_true() {
-    settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, true);
-    UserDto user = db.users().insertUser(u -> u
-      .setActive(false)
-      .setOnboarded(true));
-    createDefaultGroup();
-
-    underTest.createAndCommit(db.getSession(), NewUser.builder()
-      .setLogin(user.getLogin())
-      .setName("name")
-      .build(), u -> {
-    });
-
-    assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isFalse();
-  }
-
   private GroupDto createDefaultGroup() {
     return db.users().insertDefaultGroup(db.getDefaultOrganization());
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java
new file mode 100644 (file)
index 0000000..7a02136
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.user;
+
+import com.google.common.collect.Multimap;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.GroupTesting;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.LocalAuthentication;
+import org.sonar.server.authentication.LocalAuthentication.HashMethod;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.organization.TestOrganizationFlags;
+import org.sonar.server.user.index.UserIndexer;
+import org.sonar.server.usergroups.DefaultGroupFinder;
+
+import static java.lang.String.format;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.core.config.CorePropertyDefinitions.ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS;
+
+public class UserUpdaterReactivateTest {
+
+  private System2 system2 = new AlwaysIncreasingSystem2();
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public EsTester es = EsTester.create();
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private DbClient dbClient = db.getDbClient();
+  private NewUserNotifier newUserNotifier = mock(NewUserNotifier.class);
+  private DbSession session = db.getSession();
+  private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
+  private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
+  private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
+  private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+  private MapSettings settings = new MapSettings();
+  private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
+  private UserUpdater underTest = new UserUpdater(newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, organizationCreation,
+    new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);
+
+  @Test
+  public void reactivate_user() {
+    UserDto user = db.users().insertUser(u -> u.setActive(false));
+    createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin("marius")
+      .setName("Marius2")
+      .setEmail("marius2@mail.com")
+      .setPassword("password2")
+      .build(),
+      u -> {
+      });
+
+    UserDto reloaded = dbClient.userDao().selectByUuid(session, user.getUuid());
+    assertThat(reloaded.isActive()).isTrue();
+    assertThat(reloaded.getLogin()).isEqualTo("marius");
+    assertThat(reloaded.getName()).isEqualTo("Marius2");
+    assertThat(reloaded.getEmail()).isEqualTo("marius2@mail.com");
+    assertThat(reloaded.getScmAccounts()).isNull();
+    assertThat(reloaded.isLocal()).isTrue();
+    assertThat(reloaded.getSalt()).isNull();
+    assertThat(reloaded.getHashMethod()).isEqualTo(HashMethod.BCRYPT.name());
+    assertThat(reloaded.getCryptedPassword()).isNotNull().isNotEqualTo("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg");
+    assertThat(reloaded.getCreatedAt()).isEqualTo(user.getCreatedAt());
+    assertThat(reloaded.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
+  }
+
+  @Test
+  public void reactivate_user_not_having_password() {
+    UserDto user = db.users().insertDisabledUser(u -> u.setSalt(null).setCryptedPassword(null));
+    createDefaultGroup();
+
+    UserDto dto = underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .build(),
+      u -> {
+      });
+
+    assertThat(dto.isActive()).isTrue();
+    assertThat(dto.getName()).isEqualTo(user.getName());
+    assertThat(dto.getScmAccounts()).isNull();
+    assertThat(dto.getSalt()).isNull();
+    assertThat(dto.getCryptedPassword()).isNull();
+    assertThat(dto.getCreatedAt()).isEqualTo(user.getCreatedAt());
+    assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
+  }
+
+  @Test
+  public void reactivate_user_with_external_provider() {
+    UserDto user = db.users().insertDisabledUser(u -> u.setLocal(true));
+    createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD"))
+      .build(), u -> {
+      });
+    session.commit();
+
+    UserDto dto = dbClient.userDao().selectByUuid(session, user.getUuid());
+    assertThat(dto.isLocal()).isFalse();
+    assertThat(dto.getExternalId()).isEqualTo("ABCD");
+    assertThat(dto.getExternalLogin()).isEqualTo("john");
+    assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
+  }
+
+  @Test
+  public void reactivate_user_using_same_external_info_but_was_local() {
+    UserDto user = db.users().insertDisabledUser(u -> u.setLocal(true)
+      .setExternalId("ABCD")
+      .setExternalLogin("john")
+      .setExternalIdentityProvider("github"));
+    createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD"))
+      .build(), u -> {
+      });
+    session.commit();
+
+    UserDto dto = dbClient.userDao().selectByUuid(session, user.getUuid());
+    assertThat(dto.isLocal()).isFalse();
+    assertThat(dto.getExternalId()).isEqualTo("ABCD");
+    assertThat(dto.getExternalLogin()).isEqualTo("john");
+    assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
+  }
+
+  @Test
+  public void reactivate_user_with_local_provider() {
+    UserDto user = db.users().insertDisabledUser(u -> u.setLocal(false)
+      .setExternalId("ABCD")
+      .setExternalLogin("john")
+      .setExternalIdentityProvider("github"));
+    createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .build(), u -> {
+      });
+    session.commit();
+
+    UserDto dto = dbClient.userDao().selectByUuid(session, user.getUuid());
+    assertThat(dto.isLocal()).isTrue();
+    assertThat(dto.getExternalId()).isEqualTo(user.getLogin());
+    assertThat(dto.getExternalLogin()).isEqualTo(user.getLogin());
+    assertThat(dto.getExternalIdentityProvider()).isEqualTo("sonarqube");
+  }
+
+  @Test
+  public void fail_to_reactivate_user_if_active() {
+    UserDto user = db.users().insertUser();
+    createDefaultGroup();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(format("An active user with login '%s' already exists", user.getLogin()));
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .build(), u -> {
+      });
+  }
+
+  @Test
+  public void associate_default_groups_when_reactivating_user_and_organizations_are_disabled() {
+    organizationFlags.setEnabled(false);
+    UserDto userDto = db.users().insertDisabledUser();
+    db.organizations().insertForUuid("org1");
+    GroupDto groupDto = db.users().insertGroup(GroupTesting.newGroupDto().setName("sonar-devs").setOrganizationUuid("org1"));
+    db.users().insertMember(groupDto, userDto);
+    GroupDto defaultGroup = createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), userDto, NewUser.builder()
+      .setLogin(userDto.getLogin())
+      .setName(userDto.getName())
+      .build(), u -> {
+      });
+    session.commit();
+
+    Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, singletonList(userDto.getLogin()));
+    assertThat(groups.get(userDto.getLogin()).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isTrue();
+  }
+
+  @Test
+  public void does_not_associate_default_groups_when_reactivating_user_and_organizations_are_enabled() {
+    organizationFlags.setEnabled(true);
+    UserDto userDto = db.users().insertDisabledUser();
+    db.organizations().insertForUuid("org1");
+    GroupDto groupDto = db.users().insertGroup(GroupTesting.newGroupDto().setName("sonar-devs").setOrganizationUuid("org1"));
+    db.users().insertMember(groupDto, userDto);
+    GroupDto defaultGroup = createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), userDto, NewUser.builder()
+      .setLogin(userDto.getLogin())
+      .setName(userDto.getName())
+      .build(), u -> {
+      });
+    session.commit();
+
+    Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, singletonList(userDto.getLogin()));
+    assertThat(groups.get(userDto.getLogin()).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isFalse();
+  }
+
+  @Test
+  public void add_user_as_member_of_default_organization_when_reactivating_user_and_organizations_are_disabled() {
+    organizationFlags.setEnabled(false);
+    UserDto user = db.users().insertDisabledUser();
+    createDefaultGroup();
+
+    UserDto dto = underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder().setLogin(user.getLogin()).setName(user.getName()).build(), u -> {
+    });
+
+    assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isPresent();
+  }
+
+  @Test
+  public void does_not_add_user_as_member_of_default_organization_when_reactivating_user_and_organizations_are_enabled() {
+    organizationFlags.setEnabled(true);
+    UserDto user = db.users().insertDisabledUser();
+    createDefaultGroup();
+
+    UserDto dto = underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder().setLogin(user.getLogin()).setName(user.getName()).build(), u -> {
+    });
+
+    assertThat(dbClient.organizationMemberDao().select(db.getSession(), defaultOrganizationProvider.get().getUuid(), dto.getId())).isNotPresent();
+  }
+
+  @Test
+  public void reactivate_not_onboarded_user_if_onboarding_setting_is_set_to_false() {
+    settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, false);
+    UserDto user = db.users().insertDisabledUser(u -> u.setOnboarded(false));
+    createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .build(), u -> {
+      });
+
+    assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isTrue();
+  }
+
+  @Test
+  public void reactivate_onboarded_user_if_onboarding_setting_is_set_to_true() {
+    settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, true);
+    UserDto user = db.users().insertDisabledUser(u -> u.setOnboarded(true));
+    createDefaultGroup();
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+      .setLogin(user.getLogin())
+      .setName(user.getName())
+      .build(), u -> {
+      });
+
+    assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isFalse();
+  }
+
+  @Test
+  public void fail_to_reactivate_user_when_login_already_exists() {
+    createDefaultGroup();
+    UserDto user = db.users().insertUser(u -> u.setActive(false));
+    UserDto existingUser = db.users().insertUser(u -> u.setLogin("existing_login"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A user with login 'existing_login' already exists");
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+        .setLogin(existingUser.getLogin())
+        .setName("Marius2")
+        .setPassword("password2")
+        .build(),
+      u -> {
+      });
+  }
+
+  @Test
+  public void fail_to_reactivate_user_when_external_id_and_external_provider_already_exists() {
+    createDefaultGroup();
+    UserDto user = db.users().insertUser(u -> u.setActive(false));
+    UserDto existingUser = db.users().insertUser(u -> u.setExternalId("existing_external_id").setExternalIdentityProvider("existing_external_provider"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A user with provider id 'existing_external_id' and identity provider 'existing_external_provider' already exists");
+
+    underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder()
+        .setLogin(user.getLogin())
+        .setName("Marius2")
+        .setExternalIdentity(new ExternalIdentity(existingUser.getExternalIdentityProvider(), existingUser.getExternalLogin(), existingUser.getExternalId()))
+        .build(),
+      u -> {
+      });
+  }
+
+  private GroupDto createDefaultGroup() {
+    return db.users().insertDefaultGroup(db.getDefaultOrganization());
+  }
+
+}
index 420daca6deb19dcd35a66ce0948dc6bb3dc30376..4545218baa74c5b3559590707e7de72fabcd1eb7 100644 (file)
@@ -25,14 +25,15 @@ import org.elasticsearch.search.SearchHit;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.mockito.ArgumentCaptor;
 import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.platform.NewUserHandler;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.db.property.PropertyQuery;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserTesting;
@@ -50,8 +51,10 @@ import org.sonar.server.usergroups.DefaultGroupFinder;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
 import static org.assertj.core.data.MapEntry.entry;
 import static org.mockito.Mockito.mock;
+import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE;
 import static org.sonar.db.user.UserTesting.newLocalUser;
 import static org.sonar.db.user.UserTesting.newUserDto;
 
@@ -85,26 +88,21 @@ public class UserUpdaterUpdateTest {
   @Test
   public void update_user() {
     UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
-      .setScmAccounts(asList("ma", "marius33"))
-      .setSalt("79bd6a8e79fb8c76ac8b121cc7e8e11ad1af8365")
-      .setCryptedPassword("650d2261c98361e2f67f90ce5c65a95e7d8ea2fg"));
+      .setScmAccounts(asList("ma", "marius33")));
     createDefaultGroup();
+    userIndexer.indexOnStartup(null);
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .setScmAccounts(asList("ma2")), u -> {
-    });
+      .setScmAccounts(singletonList("ma2")), u -> {
+      });
 
     UserDto updatedUser = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(updatedUser.isActive()).isTrue();
     assertThat(updatedUser.getName()).isEqualTo("Marius2");
     assertThat(updatedUser.getEmail()).isEqualTo("marius2@mail.com");
     assertThat(updatedUser.getScmAccountsAsList()).containsOnly("ma2");
-
-    assertThat(updatedUser.getSalt()).isNotEqualTo(user.getSalt());
-    assertThat(updatedUser.getCryptedPassword()).isNotEqualTo(user.getCryptedPassword());
     assertThat(updatedUser.getCreatedAt()).isEqualTo(user.getCreatedAt());
     assertThat(updatedUser.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
 
@@ -122,14 +120,15 @@ public class UserUpdaterUpdateTest {
     UserDto user = db.users().insertUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@email.com")
-      .setExternalIdentity(new ExternalIdentity("github", "john")), u -> {
-    });
+      .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD")), u -> {
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
-    assertThat(dto.getExternalIdentity()).isEqualTo("john");
+    assertThat(dto.getExternalId()).isEqualTo("ABCD");
+    assertThat(dto.getExternalLogin()).isEqualTo("john");
     assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
     assertThat(dto.getUpdatedAt()).isGreaterThan(user.getCreatedAt());
   }
@@ -139,14 +138,15 @@ public class UserUpdaterUpdateTest {
     UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@email.com")
-      .setExternalIdentity(new ExternalIdentity("github", "john")), u -> {
-    });
+      .setExternalIdentity(new ExternalIdentity("github", "john", "ABCD")), u -> {
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
-    assertThat(dto.getExternalIdentity()).isEqualTo("john");
+    assertThat(dto.getExternalId()).isEqualTo("ABCD");
+    assertThat(dto.getExternalLogin()).isEqualTo("john");
     assertThat(dto.getExternalIdentityProvider()).isEqualTo("github");
     // Password must be removed
     assertThat(dto.getCryptedPassword()).isNull();
@@ -155,68 +155,98 @@ public class UserUpdaterUpdateTest {
   }
 
   @Test
-  public void reactivate_user_on_update() {
+  public void update_user_with_scm_accounts_containing_blank_entry() {
     UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
-      .setScmAccounts(asList("ma", "marius33"))
-      .setSalt("salt")
-      .setCryptedPassword("crypted password"));
+      .setScmAccounts(asList("ma", "marius33")));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@mail.com")
       .setPassword("password2")
-      .setScmAccounts(asList("ma2")), u -> {
-    });
+      .setScmAccounts(asList("ma2", "", null)), u -> {
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
-    assertThat(dto.isActive()).isTrue();
-    assertThat(dto.getName()).isEqualTo("Marius2");
-    assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
     assertThat(dto.getScmAccountsAsList()).containsOnly("ma2");
+  }
+
+  @Test
+  public void update_only_login() {
+    UserDto oldUser = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
+    createDefaultGroup();
+
+    underTest.updateAndCommit(session, oldUser, new UpdateUser()
+      .setLogin("new_login"), u -> {
+      });
 
-    assertThat(dto.getSalt()).isNotEqualTo(user.getSalt());
-    assertThat(dto.getCryptedPassword()).isNotEqualTo(user.getCryptedPassword());
-    assertThat(dto.getCreatedAt()).isEqualTo(user.getCreatedAt());
-    assertThat(dto.getUpdatedAt()).isGreaterThan(user.getUpdatedAt());
+    assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN)).isNull();
+    UserDto dto = dbClient.userDao().selectByUuid(session, oldUser.getUuid());
+    assertThat(dto.getLogin()).isEqualTo("new_login");
+    // Following fields has not changed
+    assertThat(dto.getName()).isEqualTo(oldUser.getName());
+    assertThat(dto.getEmail()).isEqualTo(oldUser.getEmail());
+    assertThat(dto.getScmAccountsAsList()).containsAll(oldUser.getScmAccountsAsList());
+    assertThat(dto.getSalt()).isEqualTo(oldUser.getSalt());
+    assertThat(dto.getCryptedPassword()).isEqualTo(oldUser.getCryptedPassword());
+  }
+
+  @Test
+  public void update_index_when_updating_user_login() {
+    UserDto oldUser = db.users().insertUser();
+    createDefaultGroup();
+    userIndexer.indexOnStartup(null);
+
+    underTest.updateAndCommit(session, oldUser, new UpdateUser()
+      .setLogin("new_login"), u -> {
+      });
 
     List<SearchHit> indexUsers = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER);
     assertThat(indexUsers).hasSize(1);
     assertThat(indexUsers.get(0).getSource())
-      .contains(
-        entry("login", DEFAULT_LOGIN),
-        entry("name", "Marius2"),
-        entry("email", "marius2@mail.com"));
+      .contains(entry("login", "new_login"));
   }
 
   @Test
-  public void update_user_with_scm_accounts_containing_blank_entry() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
-      .setScmAccounts(asList("ma", "marius33")));
+  public void update_default_assignee_when_updating_login() {
     createDefaultGroup();
-
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
-      .setName("Marius2")
-      .setEmail("marius2@mail.com")
-      .setPassword("password2")
-      .setScmAccounts(asList("ma2", "", null)), u -> {
-    });
-
-    UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
-    assertThat(dto.getScmAccountsAsList()).containsOnly("ma2");
+    UserDto oldUser = db.users().insertUser();
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    ComponentDto anotherProject = db.components().insertPrivateProject();
+    db.properties().insertProperties(
+      new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue(oldUser.getLogin()),
+      new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue(oldUser.getLogin()).setResourceId(project1.getId()),
+      new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue(oldUser.getLogin()).setResourceId(project2.getId()),
+      new PropertyDto().setKey(DEFAULT_ISSUE_ASSIGNEE).setValue("another login").setResourceId(anotherProject.getId())
+      );
+    userIndexer.indexOnStartup(null);
+
+    underTest.updateAndCommit(session, oldUser, new UpdateUser()
+      .setLogin("new_login"), u -> {
+      });
+
+    assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().setKey(DEFAULT_ISSUE_ASSIGNEE).build(), db.getSession()))
+      .extracting(PropertyDto::getValue, PropertyDto::getResourceId)
+      .containsOnly(
+        tuple("new_login", null),
+        tuple("new_login", project1.getId()),
+        tuple("new_login", project2.getId()),
+        tuple("another login", anotherProject.getId())
+      );
   }
 
   @Test
   public void update_only_user_name() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
       .setScmAccounts(asList("ma", "marius33"))
       .setSalt("salt")
       .setCryptedPassword("crypted password"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2"), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(dto.getName()).isEqualTo("Marius2");
@@ -230,15 +260,15 @@ public class UserUpdaterUpdateTest {
 
   @Test
   public void update_only_user_email() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
       .setScmAccounts(asList("ma", "marius33"))
       .setSalt("salt")
       .setCryptedPassword("crypted password"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setEmail("marius2@mail.com"), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(dto.getEmail()).isEqualTo("marius2@mail.com");
@@ -252,15 +282,15 @@ public class UserUpdaterUpdateTest {
 
   @Test
   public void update_only_scm_accounts() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
       .setScmAccounts(asList("ma", "marius33"))
       .setSalt("salt")
       .setCryptedPassword("crypted password"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setScmAccounts(asList("ma2")), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(dto.getScmAccountsAsList()).containsOnly("ma2");
@@ -274,13 +304,13 @@ public class UserUpdaterUpdateTest {
 
   @Test
   public void update_scm_accounts_with_same_values() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
       .setScmAccounts(asList("ma", "marius33")));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setScmAccounts(asList("ma", "marius33")), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(dto.getScmAccountsAsList()).containsOnly("ma", "marius33");
@@ -288,13 +318,13 @@ public class UserUpdaterUpdateTest {
 
   @Test
   public void remove_scm_accounts() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
       .setScmAccounts(asList("ma", "marius33")));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setScmAccounts(null), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(dto.getScmAccounts()).isNull();
@@ -302,15 +332,15 @@ public class UserUpdaterUpdateTest {
 
   @Test
   public void update_only_user_password() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr")
       .setScmAccounts(asList("ma", "marius33"))
       .setSalt("salt")
       .setCryptedPassword("crypted password"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setPassword("password2"), u -> {
-    });
+      });
 
     UserDto dto = dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN);
     assertThat(dto.getSalt()).isNotEqualTo("salt");
@@ -323,50 +353,66 @@ public class UserUpdaterUpdateTest {
   }
 
   @Test
-  public void update_only_external_identity_id() {
-    db.users().insertUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
-      .setExternalIdentity("john")
+  public void update_only_external_id() {
+    UserDto user = db.users().insertUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
+      .setExternalId("1234")
+      .setExternalLogin("john.smith")
       .setExternalIdentityProvider("github"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN).setExternalIdentity(new ExternalIdentity("github", "john.smith")), u -> {
+    underTest.updateAndCommit(session, user, new UpdateUser().setExternalIdentity(new ExternalIdentity("github", "john.smith", "ABCD")), u -> {
     });
 
     assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN))
-      .extracting(UserDto::getExternalIdentity, UserDto::getExternalIdentityProvider)
+      .extracting(UserDto::getExternalId)
+      .containsOnly("ABCD");
+  }
+
+  @Test
+  public void update_only_external_login() {
+    UserDto user = db.users().insertUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
+      .setExternalId("ABCD")
+      .setExternalLogin("john")
+      .setExternalIdentityProvider("github"));
+    createDefaultGroup();
+
+    underTest.updateAndCommit(session, user, new UpdateUser().setExternalIdentity(new ExternalIdentity("github", "john.smith", "ABCD")), u -> {
+    });
+
+    assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN))
+      .extracting(UserDto::getExternalLogin, UserDto::getExternalIdentityProvider)
       .containsOnly("john.smith", "github");
   }
 
   @Test
   public void update_only_external_identity_provider() {
-    db.users().insertUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
-      .setExternalIdentity("john")
+    UserDto user = db.users().insertUser(UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
+      .setExternalId("ABCD")
+      .setExternalLogin("john")
       .setExternalIdentityProvider("github"));
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN).setExternalIdentity(new ExternalIdentity("bitbucket", "john")), u -> {
+    underTest.updateAndCommit(session, user, new UpdateUser().setExternalIdentity(new ExternalIdentity("bitbucket", "john", "ABCD")), u -> {
     });
 
     assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN))
-      .extracting(UserDto::getExternalIdentity, UserDto::getExternalIdentityProvider)
+      .extracting(UserDto::getExternalLogin, UserDto::getExternalIdentityProvider)
       .containsOnly("john", "bitbucket");
   }
 
   @Test
   public void does_not_update_user_when_no_change() {
     UserDto user = UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
-      .setExternalIdentity("john")
-      .setExternalIdentityProvider("github")
       .setScmAccounts(asList("ma1", "ma2"));
     db.users().insertUser(user);
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(user.getLogin())
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName(user.getName())
       .setEmail(user.getEmail())
       .setScmAccounts(user.getScmAccountsAsList())
-      .setExternalIdentity(new ExternalIdentity(user.getExternalIdentityProvider(), user.getExternalIdentity())), u -> {
-    });
+      .setExternalIdentity(new ExternalIdentity(user.getExternalIdentityProvider(), user.getExternalLogin(), user.getExternalId())), u -> {
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN).getUpdatedAt()).isEqualTo(user.getUpdatedAt());
   }
@@ -374,18 +420,16 @@ public class UserUpdaterUpdateTest {
   @Test
   public void does_not_update_user_when_no_change_and_scm_account_reordered() {
     UserDto user = UserTesting.newExternalUser(DEFAULT_LOGIN, "Marius", "marius@email.com")
-      .setExternalIdentity("john")
-      .setExternalIdentityProvider("github")
       .setScmAccounts(asList("ma1", "ma2"));
     db.users().insertUser(user);
     createDefaultGroup();
 
-    underTest.updateAndCommit(session, UpdateUser.create(user.getLogin())
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName(user.getName())
       .setEmail(user.getEmail())
       .setScmAccounts(asList("ma2", "ma1"))
-      .setExternalIdentity(new ExternalIdentity(user.getExternalIdentityProvider(), user.getExternalIdentity())), u -> {
-    });
+      .setExternalIdentity(new ExternalIdentity(user.getExternalIdentityProvider(), user.getExternalLogin(), user.getExternalId())), u -> {
+      });
 
     assertThat(dbClient.userDao().selectByLogin(session, DEFAULT_LOGIN).getUpdatedAt()).isEqualTo(user.getUpdatedAt());
   }
@@ -397,52 +441,52 @@ public class UserUpdaterUpdateTest {
       .setScmAccounts(asList("ma", "marius33")));
     UserDto otherUser = db.users().insertUser();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@mail.com")
       .setPassword("password2")
       .setScmAccounts(asList("ma2")), u -> {
-    }, otherUser);
+      }, otherUser);
 
-    assertThat(es.getIds(UserIndexDefinition.INDEX_TYPE_USER)).containsExactlyInAnyOrder(user.getLogin(), otherUser.getLogin());
+    assertThat(es.getIds(UserIndexDefinition.INDEX_TYPE_USER)).containsExactlyInAnyOrder(user.getUuid(), otherUser.getUuid());
   }
 
   @Test
   public void fail_to_set_null_password_when_local_user() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
     createDefaultGroup();
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Password can't be empty");
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN).setPassword(null), u -> {
+    underTest.updateAndCommit(session, user, new UpdateUser().setPassword(null), u -> {
     });
   }
 
   @Test
   public void fail_to_update_password_when_user_is_not_local() {
-    db.users().insertUser(newUserDto()
+    UserDto user = db.users().insertUser(newUserDto()
       .setLogin(DEFAULT_LOGIN)
       .setLocal(false));
     createDefaultGroup();
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Password cannot be changed when external authentication is used");
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN).setPassword("password2"), u -> {
+    underTest.updateAndCommit(session, user, new UpdateUser().setPassword("password2"), u -> {
     });
   }
 
   @Test
   public void not_associate_default_group_when_updating_user() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com"));
     GroupDto defaultGroup = createDefaultGroup();
 
     // Existing user, he has no group, and should not be associated to the default one
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@mail.com")
       .setPassword("password2")
       .setScmAccounts(asList("ma2")), u -> {
-    });
+      });
 
     Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN));
     assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).isFalse();
@@ -458,12 +502,12 @@ public class UserUpdaterUpdateTest {
     Multimap<String, String> groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN));
     assertThat(groups.get(DEFAULT_LOGIN).stream().anyMatch(g -> g.equals(defaultGroup.getName()))).as("Current user groups : %s", groups.get(defaultGroup.getName())).isTrue();
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@mail.com")
       .setPassword("password2")
       .setScmAccounts(asList("ma2")), u -> {
-    });
+      });
 
     // Nothing as changed
     groups = dbClient.groupMembershipDao().selectGroupsByLogins(session, asList(DEFAULT_LOGIN));
@@ -472,56 +516,95 @@ public class UserUpdaterUpdateTest {
 
   @Test
   public void fail_to_update_user_when_scm_account_is_already_used() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com").setScmAccounts(singletonList("ma")));
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@email.com").setScmAccounts(singletonList("ma")));
     db.users().insertUser(newLocalUser("john", "John", "john@email.com").setScmAccounts(singletonList("jo")));
     createDefaultGroup();
 
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("The scm account 'jo' is already used by user(s) : 'John (john)'");
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setName("Marius2")
       .setEmail("marius2@mail.com")
       .setPassword("password2")
       .setScmAccounts(asList("jo")), u -> {
-    });
+      });
   }
 
   @Test
   public void fail_to_update_user_when_scm_account_is_user_login() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
     createDefaultGroup();
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Login and email are automatically considered as SCM accounts");
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN).setScmAccounts(asList(DEFAULT_LOGIN)), u -> {
+    underTest.updateAndCommit(session, user, new UpdateUser().setScmAccounts(asList(DEFAULT_LOGIN)), u -> {
     });
   }
 
   @Test
   public void fail_to_update_user_when_scm_account_is_existing_user_email() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
     createDefaultGroup();
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Login and email are automatically considered as SCM accounts");
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN).setScmAccounts(asList("marius@lesbronzes.fr")), u -> {
+    underTest.updateAndCommit(session, user, new UpdateUser().setScmAccounts(asList("marius@lesbronzes.fr")), u -> {
     });
   }
 
   @Test
   public void fail_to_update_user_when_scm_account_is_new_user_email() {
-    db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
+    UserDto user = db.users().insertUser(newLocalUser(DEFAULT_LOGIN, "Marius", "marius@lesbronzes.fr"));
     createDefaultGroup();
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Login and email are automatically considered as SCM accounts");
 
-    underTest.updateAndCommit(session, UpdateUser.create(DEFAULT_LOGIN)
+    underTest.updateAndCommit(session, user, new UpdateUser()
       .setEmail("marius@newmail.com")
       .setScmAccounts(asList("marius@newmail.com")), u -> {
+      });
+  }
+
+  @Test
+  public void fail_to_update_login_when_format_is_invalid() {
+    UserDto user = db.users().insertUser();
+    createDefaultGroup();
+
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("Use only letters, numbers, and .-_@ please.");
+
+    underTest.updateAndCommit(session, user, new UpdateUser().setLogin("With space"), u -> {
     });
   }
 
+  @Test
+  public void fail_to_update_user_when_login_already_exists() {
+    createDefaultGroup();
+    UserDto user = db.users().insertUser(u -> u.setActive(false));
+    UserDto existingUser = db.users().insertUser(u -> u.setLogin("existing_login"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A user with login 'existing_login' already exists");
+
+    underTest.updateAndCommit(session, user, new UpdateUser().setLogin(existingUser.getLogin()), u -> {
+    });
+  }
+
+  @Test
+  public void fail_to_update_user_when_external_id_and_external_provider_already_exists() {
+    createDefaultGroup();
+    UserDto user = db.users().insertUser(u -> u.setActive(false));
+    UserDto existingUser = db.users().insertUser(u -> u.setExternalId("existing_external_id").setExternalIdentityProvider("existing_external_provider"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A user with provider id 'existing_external_id' and identity provider 'existing_external_provider' already exists");
+
+    underTest.updateAndCommit(session, user, new UpdateUser()
+      .setExternalIdentity(new ExternalIdentity(existingUser.getExternalIdentityProvider(), existingUser.getExternalLogin(), existingUser.getExternalId())), u -> {
+      });
+  }
+
   private GroupDto createDefaultGroup() {
     return db.users().insertDefaultGroup(db.getDefaultOrganization());
   }
index 8b4e6e9025345efe06c6375aada5f939a5a41769..1a827dab824c484fa505ec9dc8eeaf6423cd96e1 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Locale;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
+import org.sonar.core.util.Uuids;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.es.SearchOptions;
 
@@ -47,33 +48,6 @@ public class UserIndexTest {
   private UserIndex underTest = new UserIndex(es.client(), System2.INSTANCE);
   private UserQuery.Builder userQuery = UserQuery.builder();
 
-  @Test
-  public void get_nullable_by_login() {
-    UserDoc user1 = newUser(USER1_LOGIN, asList("scmA", "scmB"));
-    es.putDocuments(INDEX_TYPE_USER, user1);
-    es.putDocuments(INDEX_TYPE_USER, newUser(USER2_LOGIN, Collections.emptyList()));
-
-    UserDoc userDoc = underTest.getNullableByLogin(USER1_LOGIN);
-    assertThat(userDoc).isNotNull();
-    assertThat(userDoc.login()).isEqualTo(user1.login());
-    assertThat(userDoc.name()).isEqualTo(user1.name());
-    assertThat(userDoc.email()).isEqualTo(user1.email());
-    assertThat(userDoc.active()).isTrue();
-    assertThat(userDoc.scmAccounts()).isEqualTo(user1.scmAccounts());
-
-    assertThat(underTest.getNullableByLogin("")).isNull();
-    assertThat(underTest.getNullableByLogin("unknown")).isNull();
-  }
-
-  @Test
-  public void getNullableByLogin_is_case_sensitive() {
-    UserDoc user1 = newUser(USER1_LOGIN, asList("scmA", "scmB"));
-    es.putDocuments(INDEX_TYPE_USER, user1);
-
-    assertThat(underTest.getNullableByLogin(USER1_LOGIN)).isNotNull();
-    assertThat(underTest.getNullableByLogin("UsEr1")).isNull();
-  }
-
   @Test
   public void getAtMostThreeActiveUsersForScmAccount_returns_the_users_with_specified_scm_account() {
     UserDoc user1 = newUser("user1", asList("user_1", "u1"));
@@ -199,6 +173,7 @@ public class UserIndexTest {
 
   private static UserDoc newUser(String login, List<String> scmAccounts) {
     return new UserDoc()
+      .setUuid(Uuids.createFast())
       .setLogin(login)
       .setName(login.toUpperCase(Locale.ENGLISH))
       .setEmail(login + "@mail.com")
@@ -209,6 +184,7 @@ public class UserIndexTest {
 
   private static UserDoc newUser(String login, String email, List<String> scmAccounts) {
     return new UserDoc()
+      .setUuid(Uuids.createFast())
       .setLogin(login)
       .setName(login.toUpperCase(Locale.ENGLISH))
       .setEmail(email)
index 5b047b5d42b4cac673269cf9cbb1097bcb2b89b2..2a9d822d03ec63559ed2f7f6bff9cefb327ad7bd 100644 (file)
@@ -25,12 +25,14 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
 import org.sonar.db.DbTester;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
 import org.sonar.server.es.EsTester;
 
 import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
 
 public class UserIndexerTest {
 
@@ -53,19 +55,38 @@ public class UserIndexerTest {
 
   @Test
   public void indexOnStartup_adds_all_users_to_index() {
-    UserDto user = db.users().insertUser(u -> u
-      .setScmAccounts(asList("user_1", "u1")));
+    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("user_1", "u1")));
 
     underTest.indexOnStartup(new HashSet<>());
 
     List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
     assertThat(docs).hasSize(1);
     UserDoc doc = docs.get(0);
+    assertThat(doc.uuid()).isEqualTo(user.getUuid());
     assertThat(doc.login()).isEqualTo(user.getLogin());
     assertThat(doc.name()).isEqualTo(user.getName());
     assertThat(doc.email()).isEqualTo(user.getEmail());
     assertThat(doc.active()).isEqualTo(user.isActive());
     assertThat(doc.scmAccounts()).isEqualTo(user.getScmAccountsAsList());
+    assertThat(doc.organizationUuids()).isEmpty();
+  }
+
+  @Test
+  public void indexOnStartup_adds_all_users_with_organizations() {
+    OrganizationDto organization1 = db.organizations().insert();
+    OrganizationDto organization2 = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    db.organizations().addMember(organization1, user);
+    db.organizations().addMember(organization2, user);
+
+    underTest.indexOnStartup(new HashSet<>());
+
+    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    assertThat(docs).hasSize(1);
+    UserDoc doc = docs.get(0);
+    assertThat(doc.uuid()).isEqualTo(user.getUuid());
+    assertThat(doc.login()).isEqualTo(user.getLogin());
+    assertThat(doc.organizationUuids()).containsExactlyInAnyOrder(organization1.getUuid(), organization2.getUuid());
   }
 
   @Test
@@ -76,21 +97,47 @@ public class UserIndexerTest {
     underTest.commitAndIndex(db.getSession(), user);
 
     List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
-    assertThat(docs).hasSize(1);
-    assertThat(docs).extracting(UserDoc::login)
-      .containsExactly(user.getLogin())
-      .doesNotContain(anotherUser.getLogin());
+    assertThat(docs)
+      .extracting(UserDoc::uuid)
+      .containsExactlyInAnyOrder(user.getUuid())
+    .doesNotContain(anotherUser.getUuid());
+  }
+
+  @Test
+  public void commitAndIndex_single_user_belonging_to_organizations() {
+    OrganizationDto organization1 = db.organizations().insert();
+    OrganizationDto organization2 = db.organizations().insert();
+    UserDto user = db.users().insertUser();
+    db.organizations().addMember(organization1, user);
+    db.organizations().addMember(organization2, user);
+    UserDto anotherUser = db.users().insertUser();
+    db.organizations().addMember(organization1, anotherUser);
+
+    underTest.commitAndIndex(db.getSession(), user);
+
+    List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
+    assertThat(docs)
+      .extracting(UserDoc::uuid, UserDoc::organizationUuids)
+      .containsExactlyInAnyOrder(tuple(user.getUuid(), asList(organization1.getUuid(), organization2.getUuid())));
   }
 
   @Test
   public void commitAndIndex_multiple_users() {
-    UserDto user1 = db.getDbClient().userDao().insert(db.getSession(), UserTesting.newUserDto());
-    UserDto user2 = db.getDbClient().userDao().insert(db.getSession(), UserTesting.newUserDto());
+    OrganizationDto organization1 = db.organizations().insert();
+    UserDto user1 = db.users().insertUser();
+    db.organizations().addMember(organization1, user1);
+    OrganizationDto organization2 = db.organizations().insert();
+    UserDto user2 = db.users().insertUser();
+    db.organizations().addMember(organization2, user2);
 
     underTest.commitAndIndex(db.getSession(), asList(user1, user2));
 
     List<UserDoc> docs = es.getDocuments(UserIndexDefinition.INDEX_TYPE_USER, UserDoc.class);
-    assertThat(docs).extracting(UserDoc::login).containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin());
+    assertThat(docs)
+      .extracting(UserDoc::login, UserDoc::organizationUuids)
+      .containsExactlyInAnyOrder(
+        tuple(user1.getLogin(), singletonList(organization1.getUuid())),
+        tuple(user2.getLogin(), singletonList(organization2.getUuid())));
     assertThat(db.countRowsOfTable(db.getSession(), "users")).isEqualTo(2);
   }
 }
index 94ad82cc651508b5160804619b5fcc867fe424eb..99d613f90a8ed4332fc0c2fc0c4b5696736a5893 100644 (file)
@@ -34,17 +34,18 @@ import org.sonar.server.organization.OrganizationCreation;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.organization.TestOrganizationFlags;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.user.ExternalIdentity;
 import org.sonar.server.user.NewUser;
 import org.sonar.server.user.NewUserNotifier;
 import org.sonar.server.user.UserUpdater;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.usergroups.DefaultGroupFinder;
-import org.sonar.server.ws.WsTester;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
 
-import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.sonar.db.user.UserTesting.newExternalUser;
+import static org.sonar.db.user.UserTesting.newLocalUser;
 
 public class ChangePasswordActionTest {
   @Rule
@@ -67,7 +68,7 @@ public class ChangePasswordActionTest {
     new MapSettings().asConfig(),
     localAuthentication);
 
-  private WsTester tester = new WsTester(new UsersWs(new ChangePasswordAction(db.getDbClient(), userUpdater, userSessionRule, localAuthentication)));
+  private WsActionTester tester = new WsActionTester(new ChangePasswordAction(db.getDbClient(), userUpdater, userSessionRule, localAuthentication));
 
   @Before
   public void setUp() {
@@ -75,80 +76,102 @@ public class ChangePasswordActionTest {
   }
 
   @Test
-  public void fail_on_missing_permission() throws Exception {
-    createUser();
-    userSessionRule.logIn("polop");
+  public void a_user_can_update_his_password() {
+    userUpdater.createAndCommit(db.getSession(), NewUser.builder()
+      .setEmail("john@email.com")
+      .setLogin("john")
+      .setName("John")
+      .setPassword("Valar Dohaeris")
+      .build(), u -> {
+    });
+    String oldCryptedPassword = db.getDbClient().userDao().selectOrFailByLogin(db.getSession(), "john").getCryptedPassword();
+    userSessionRule.logIn("john");
 
-    expectedException.expect(ForbiddenException.class);
-    tester.newPostRequest("api/users", "change_password")
+    TestResponse response = tester.newRequest()
       .setParam("login", "john")
+      .setParam("previousPassword", "Valar Dohaeris")
+      .setParam("password", "Valar Morghulis")
       .execute();
-  }
 
-  @Test
-  public void fail_on_unknown_user() throws Exception {
-    userSessionRule.logIn().setSystemAdministrator();
-
-    expectedException.expect(NotFoundException.class);
-
-    tester.newPostRequest("api/users", "change_password")
-      .setParam("login", "polop")
-      .setParam("password", "polop")
-      .execute();
+    assertThat(response.getStatus()).isEqualTo(204);
+    String newCryptedPassword = db.getDbClient().userDao().selectOrFailByLogin(db.getSession(), "john").getCryptedPassword();
+    assertThat(newCryptedPassword).isNotEqualTo(oldCryptedPassword);
   }
 
   @Test
-  public void system_administrator_can_update_password_of_user() throws Exception {
+  public void system_administrator_can_update_password_of_user() {
     userSessionRule.logIn().setSystemAdministrator();
-    createUser();
+    createLocalUser();
     String originalPassword = db.getDbClient().userDao().selectOrFailByLogin(db.getSession(), "john").getCryptedPassword();
 
-    tester.newPostRequest("api/users", "change_password")
+    tester.newRequest()
       .setParam("login", "john")
       .setParam("password", "Valar Morghulis")
-      .execute()
-      .assertNoContent();
+      .execute();
 
     String newPassword = db.getDbClient().userDao().selectOrFailByLogin(db.getSession(), "john").getCryptedPassword();
     assertThat(newPassword).isNotEqualTo(originalPassword);
   }
 
   @Test
-  public void a_user_can_update_his_password() throws Exception {
-    createUser();
-    String originalPassword = db.getDbClient().userDao().selectOrFailByLogin(db.getSession(), "john").getCryptedPassword();
+  public void fail_on_missing_permission() {
+    createLocalUser();
+    userSessionRule.logIn("polop");
 
-    userSessionRule.logIn("john");
-    tester.newPostRequest("api/users", "change_password")
+    expectedException.expect(ForbiddenException.class);
+    tester.newRequest()
       .setParam("login", "john")
-      .setParam("previousPassword", "Valar Dohaeris")
-      .setParam("password", "Valar Morghulis")
-      .execute()
-      .assertNoContent();
+      .execute();
+  }
 
-    String newPassword = db.getDbClient().userDao().selectOrFailByLogin(db.getSession(), "john").getCryptedPassword();
-    assertThat(newPassword).isNotEqualTo(originalPassword);
+  @Test
+  public void fail_on_unknown_user() {
+    userSessionRule.logIn().setSystemAdministrator();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User with login 'polop' has not been found");
+
+    tester.newRequest()
+      .setParam("login", "polop")
+      .setParam("password", "polop")
+      .execute();
+  }
+
+  @Test
+  public void fail_on_disabled_user() {
+    db.users().insertUser(u -> u.setLogin( "polop").setActive(false));
+    userSessionRule.logIn().setSystemAdministrator();
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User with login 'polop' has not been found");
+
+    tester.newRequest()
+      .setParam("login", "polop")
+      .setParam("password", "polop")
+      .execute();
   }
 
   @Test
-  public void fail_to_update_password_on_self_without_old_password() throws Exception {
-    createUser();
+  public void fail_to_update_password_on_self_without_old_password() {
+    createLocalUser();
     userSessionRule.logIn("john");
 
     expectedException.expect(IllegalArgumentException.class);
-    tester.newPostRequest("api/users", "change_password")
+
+    tester.newRequest()
       .setParam("login", "john")
       .setParam("password", "Valar Morghulis")
       .execute();
   }
 
   @Test
-  public void fail_to_update_password_on_self_with_bad_old_password() throws Exception {
-    createUser();
+  public void fail_to_update_password_on_self_with_bad_old_password() {
+    createLocalUser();
     userSessionRule.logIn("john");
 
     expectedException.expect(IllegalArgumentException.class);
-    tester.newPostRequest("api/users", "change_password")
+
+    tester.newRequest()
       .setParam("login", "john")
       .setParam("previousPassword", "I dunno")
       .setParam("password", "Valar Morghulis")
@@ -156,34 +179,19 @@ public class ChangePasswordActionTest {
   }
 
   @Test
-  public void fail_to_update_password_on_external_auth() throws Exception {
+  public void fail_to_update_password_on_external_auth() {
     userSessionRule.logIn().setSystemAdministrator();
-
-    NewUser newUser = NewUser.builder()
-      .setEmail("john@email.com")
-      .setLogin("john")
-      .setName("John")
-      .setScmAccounts(newArrayList("jn"))
-      .setExternalIdentity(new ExternalIdentity("gihhub", "john"))
-      .build();
-    userUpdater.createAndCommit(db.getSession(), newUser, u -> {
-    });
+    db.users().insertUser(newExternalUser("john", "John", "john@email.com"));
 
     expectedException.expect(BadRequestException.class);
-    tester.newPostRequest("api/users", "change_password")
+
+    tester.newRequest()
       .setParam("login", "john")
       .setParam("password", "Valar Morghulis")
       .execute();
   }
 
-  private void createUser() {
-    userUpdater.createAndCommit(db.getSession(), NewUser.builder()
-      .setEmail("john@email.com")
-      .setLogin("john")
-      .setName("John")
-      .setScmAccounts(newArrayList("jn"))
-      .setPassword("Valar Dohaeris")
-      .build(), u -> {
-    });
+  private void createLocalUser() {
+    db.users().insertUser(newLocalUser("john", "John", "john@email.com"));
   }
 }
index 5e5c5ebf71b98a24ef89ac9e0e501477686d093e..5c117ca18721cc0b6b2aa27028d14afdd034f0e9 100644 (file)
@@ -44,8 +44,8 @@ import org.sonar.server.organization.TestOrganizationFlags;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.user.NewUserNotifier;
 import org.sonar.server.user.UserUpdater;
-import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.user.ws.CreateAction.CreateRequest;
 import org.sonar.server.usergroups.DefaultGroupFinder;
@@ -54,13 +54,20 @@ import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Users.CreateWsResponse;
 import org.sonarqube.ws.Users.CreateWsResponse.User;
 
+import static java.lang.String.format;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.sonar.core.util.Protobuf.setNullable;
 import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_EMAIL;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_LOGIN;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_NAME;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_SCM_ACCOUNTS;
 
 public class CreateActionTest {
 
@@ -111,11 +118,14 @@ public class CreateActionTest {
       .extracting(User::getLogin, User::getName, User::getEmail, User::getScmAccountsList, User::getLocal)
       .containsOnly("john", "John", "john@email.com", singletonList("jn"), true);
 
-    UserDoc user = index.getNullableByLogin("john");
-    assertThat(user.login()).isEqualTo("john");
-    assertThat(user.name()).isEqualTo("John");
-    assertThat(user.email()).isEqualTo("john@email.com");
-    assertThat(user.scmAccounts()).containsOnly("jn");
+    // exists in index
+    assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+      .setQuery(boolQuery()
+        .must(termQuery(FIELD_LOGIN, "john"))
+        .must(termQuery(FIELD_NAME, "John"))
+        .must(termQuery(FIELD_EMAIL, "john@email.com"))
+        .must(termQuery(FIELD_SCM_ACCOUNTS, "jn")))
+      .get().getHits().getHits()).hasSize(1);
 
     // exists in db
     Optional<UserDto> dbUser = db.users().selectUserByLogin("john");
@@ -168,7 +178,7 @@ public class CreateActionTest {
       .build());
 
     assertThat(db.users().selectUserByLogin("john").get())
-      .extracting(UserDto::isLocal, UserDto::getExternalIdentityProvider, UserDto::getExternalIdentity, UserDto::isRoot)
+      .extracting(UserDto::isLocal, UserDto::getExternalIdentityProvider, UserDto::getExternalLogin, UserDto::isRoot)
       .containsOnly(true, "sonarqube", "john", false);
   }
 
@@ -183,7 +193,7 @@ public class CreateActionTest {
       .build());
 
     assertThat(db.users().selectUserByLogin("john").get())
-      .extracting(UserDto::isLocal, UserDto::getExternalIdentityProvider, UserDto::getExternalIdentity, UserDto::isRoot)
+      .extracting(UserDto::isLocal, UserDto::getExternalIdentityProvider, UserDto::getExternalLogin, UserDto::isRoot)
       .containsOnly(false, "sonarqube", "john", false);
   }
 
@@ -214,7 +224,7 @@ public class CreateActionTest {
       .build());
 
     assertThat(db.users().selectUserByLogin("john").get())
-      .extracting(UserDto::getExternalIdentity)
+      .extracting(UserDto::getExternalLogin)
       .containsOnly("john");
   }
 
@@ -264,6 +274,21 @@ public class CreateActionTest {
     assertThat(db.users().selectUserByLogin("john").get().isActive()).isTrue();
   }
 
+  @Test
+  public void fail_to_reactivate_user_when_active_user_exists() {
+    logInAsSystemAdministrator();
+    UserDto user = db.users().insertUser();
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage(format("An active user with login '%s' already exists", user.getLogin()));
+
+    call(CreateRequest.builder()
+      .setLogin(user.getLogin())
+      .setName("John")
+      .setPassword("1234")
+      .build());
+  }
+
   @Test
   public void fail_when_missing_login() {
     logInAsSystemAdministrator();
@@ -343,10 +368,6 @@ public class CreateActionTest {
     executeRequest("john");
   }
 
-  private void setDefaultGroupProperty(GroupDto adminGroup) {
-    settings.setProperty("sonar.defaultGroup", adminGroup.getName());
-  }
-
   private CreateWsResponse executeRequest(String login) {
     return call(CreateRequest.builder()
       .setLogin(login)
index ace7efa2150ab126b532d2fd8d98a2435424d21d..8dac16ed83cbae72bd7ca6aa01d927a45a73dc94 100644 (file)
@@ -79,7 +79,7 @@ public class CurrentActionTest {
       .setName("Obiwan Kenobi")
       .setEmail("obiwan.kenobi@starwars.com")
       .setLocal(true)
-      .setExternalIdentity("obiwan")
+      .setExternalLogin("obiwan")
       .setExternalIdentityProvider("sonarqube")
       .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))
       .setOnboarded(false));
@@ -102,7 +102,7 @@ public class CurrentActionTest {
       .setName("Obiwan Kenobi")
       .setEmail(null)
       .setLocal(true)
-      .setExternalIdentity("obiwan")
+      .setExternalLogin("obiwan")
       .setExternalIdentityProvider("sonarqube")
       .setScmAccounts((String) null));
     userSessionRule.logIn("obiwan.kenobi");
@@ -336,7 +336,7 @@ public class CurrentActionTest {
       .setName("Obiwan Kenobi")
       .setEmail("obiwan.kenobi@starwars.com")
       .setLocal(true)
-      .setExternalIdentity("obiwan.kenobi")
+      .setExternalLogin("obiwan.kenobi")
       .setExternalIdentityProvider("sonarqube")
       .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket"))
       .setOnboarded(true)
index 298506d22bab0dd4762a1e72937e28927ded8060..c5379de6bb33ca8bb0dcda2fed7e64d27ad3daf7 100644 (file)
@@ -45,13 +45,15 @@ import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
 import org.sonar.server.user.index.UserIndexer;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
 
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
+import static org.elasticsearch.index.query.QueryBuilders.termQuery;
 import static org.sonar.api.web.UserRole.CODEVIEWER;
 import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
@@ -62,6 +64,8 @@ import static org.sonar.db.permission.OrganizationPermission.SCAN;
 import static org.sonar.db.property.PropertyTesting.newUserPropertyDto;
 import static org.sonar.db.user.UserTesting.newUserDto;
 import static org.sonar.db.user.UserTokenTesting.newUserToken;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_ACTIVE;
+import static org.sonar.server.user.index.UserIndexDefinition.FIELD_UUID;
 import static org.sonar.test.JsonAssert.assertJson;
 
 public class DeactivateActionTest {
@@ -81,7 +85,6 @@ public class DeactivateActionTest {
   public UserSessionRule userSession = UserSessionRule.standalone();
 
   private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
-  private UserIndex index = new UserIndex(es.client(), system2);
   private DbClient dbClient = db.getDbClient();
   private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
   private DbSession dbSession = db.getSession();
@@ -101,7 +104,11 @@ public class DeactivateActionTest {
     deactivate(user.getLogin()).getInput();
 
     verifyThatUserIsDeactivated(user.getLogin());
-    assertThat(index.getNullableByLogin(user.getLogin()).active()).isFalse();
+    assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER)
+      .setQuery(boolQuery()
+        .must(termQuery(FIELD_UUID, user.getUuid()))
+        .must(termQuery(FIELD_ACTIVE, "false")))
+      .get().getHits().getHits()).hasSize(1);
   }
 
   @Test
@@ -173,7 +180,8 @@ public class DeactivateActionTest {
     deactivate(user.getLogin()).getInput();
 
     assertThat(db.getDbClient().permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getId())).extracting(PermissionTemplateUserDto::getUserId).isEmpty();
-    assertThat(db.getDbClient().permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, anotherTemplate.getId())).extracting(PermissionTemplateUserDto::getUserId).isEmpty();
+    assertThat(db.getDbClient().permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, anotherTemplate.getId())).extracting(PermissionTemplateUserDto::getUserId)
+      .isEmpty();
   }
 
   @Test
index 8659995132f81d7909e3f14661dedcfcfbb0f866..86f1d5a258f12e0d37e0a3794a9dafb857dfbf41 100644 (file)
@@ -45,7 +45,6 @@ import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.db.user.GroupTesting.newGroupDto;
-import static org.sonar.db.user.UserTesting.newUserDto;
 import static org.sonar.db.user.UserTokenTesting.newUserToken;
 import static org.sonar.test.JsonAssert.assertJson;
 
@@ -70,16 +69,16 @@ public class SearchActionTest {
 
   @Test
   public void test_json_example() throws Exception {
-    UserDto fmallet = db.users().insertUser(newUserDto("fmallet", "Freddy Mallet", "f@m.com")
+    UserDto fmallet = db.users().insertUser(u -> u.setLogin("fmallet").setName("Freddy Mallet").setEmail("f@m.com")
       .setActive(true)
       .setLocal(true)
       .setScmAccounts(emptyList())
-      .setExternalIdentity("fmallet")
+      .setExternalLogin("fmallet")
       .setExternalIdentityProvider("sonarqube"));
-    UserDto simon = db.users().insertUser(newUserDto("sbrandhof", "Simon", "s.brandhof@company.tld")
+    UserDto simon = db.users().insertUser(u -> u.setLogin("sbrandhof").setName("Simon").setEmail("s.brandhof@company.tld")
       .setActive(true)
       .setLocal(false)
-      .setExternalIdentity("sbrandhof@ldap.com")
+      .setExternalLogin("sbrandhof@ldap.com")
       .setExternalIdentityProvider("LDAP")
       .setScmAccounts(newArrayList("simon.brandhof", "s.brandhof@company.tld")));
     GroupDto sonarUsers = db.users().insertGroup(newGroupDto().setName("sonar-users"));
@@ -263,7 +262,7 @@ public class SearchActionTest {
   public void only_return_login_and_name_when_not_logged() throws Exception {
     userSession.anonymous();
 
-    dbClient.userDao().insert(dbSession, newUserDto("john", "John", "john@email.com"));
+    db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com"));
     dbSession.commit();
     userIndexer.indexOnStartup(null);
 
@@ -288,14 +287,13 @@ public class SearchActionTest {
       String name = String.format("User %d", index);
       List<String> scmAccounts = singletonList(String.format("user-%d", index));
 
-      UserDto userDto = dbClient.userDao().insert(dbSession, newUserDto()
-        .setActive(true)
+      UserDto userDto = db.users().insertUser(u -> u.setActive(true)
         .setEmail(email)
         .setLogin(login)
         .setName(name)
         .setScmAccounts(scmAccounts)
         .setLocal(true)
-        .setExternalIdentity(login)
+        .setExternalLogin(login)
         .setExternalIdentityProvider("sonarqube"));
       userDtos.add(userDto);
 
index 9b6637a5942b4fdb466fbdf3dc704ceefe04cbb7..46f0713e656b08ed873eabda623d24aefb346d0e 100644 (file)
@@ -240,6 +240,19 @@ public class UpdateActionTest {
   @Test
   public void fail_on_unknown_user() {
     expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User 'john' doesn't exist");
+
+    ws.newRequest()
+      .setParam("login", "john")
+      .execute();
+  }
+
+  @Test
+  public void fail_on_disabled_user() {
+    db.users().insertUser(u -> u.setLogin("john").setActive(false));
+
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User 'john' doesn't exist");
 
     ws.newRequest()
       .setParam("login", "john")
@@ -267,7 +280,7 @@ public class UpdateActionTest {
       .setScmAccounts(newArrayList("jn"))
       .setActive(true)
       .setLocal(true)
-      .setExternalIdentity("jo")
+      .setExternalLogin("jo")
       .setExternalIdentityProvider("sonarqube");
     dbClient.userDao().insert(dbSession, userDto);
     userIndexer.commitAndIndex(dbSession, userDto);
index 46fe76700fa21ef7d82b28520af578f35960f349..943400d8380e9461ee3534825a95d012fa4820e9 100644 (file)
@@ -38,6 +38,7 @@ import static org.apache.commons.lang.StringUtils.isNotBlank;
 @Immutable
 public final class UserIdentity {
 
+  private final String id;
   private final String providerLogin;
   private final String login;
   private final String name;
@@ -46,6 +47,7 @@ public final class UserIdentity {
   private final Set<String> groups;
 
   private UserIdentity(Builder builder) {
+    this.id = builder.id;
     this.providerLogin = builder.providerLogin;
     this.login = builder.login;
     this.name = builder.name;
@@ -54,6 +56,19 @@ public final class UserIdentity {
     this.groups = builder.groups;
   }
 
+  /**
+   * Optional unique ID for the related {@link IdentityProvider}.
+   * If two {@link IdentityProvider} define two users with the same ID, then users are considered as identical.
+   *
+   * When the ID is not provided, the provider login {@link #getProviderLogin()} is used.
+   *
+   * @since 7.2
+   */
+  @CheckForNull
+  public String getProviderId() {
+    return id;
+  }
+
   /**
    * Non-blank user login for the related {@link IdentityProvider}.
    */
@@ -109,6 +124,7 @@ public final class UserIdentity {
   }
 
   public static class Builder {
+    private String id;
     private String providerLogin;
     private String login;
     private String name;
@@ -119,6 +135,15 @@ public final class UserIdentity {
     private Builder() {
     }
 
+    /**
+     * @see UserIdentity#getProviderId()
+     * @since 7.2
+     */
+    public Builder setProviderId(@Nullable String id) {
+      this.id = id;
+      return this;
+    }
+
     /**
      * @see UserIdentity#getProviderLogin()
      */
@@ -175,6 +200,7 @@ public final class UserIdentity {
     }
 
     public UserIdentity build() {
+      validateId(id);
       validateProviderLogin(providerLogin);
       validateLogin(login);
       validateName(name);
@@ -182,6 +208,10 @@ public final class UserIdentity {
       return new UserIdentity(this);
     }
 
+    private static void validateId(@Nullable String id) {
+      checkArgument(id == null || id.length() <= 255, "ID is too big (255 characters max)");
+    }
+
     private static void validateProviderLogin(String providerLogin) {
       checkArgument(isNotBlank(providerLogin), "Provider login must not be blank");
       checkArgument(providerLogin.length() <= 255, "Provider login size is incorrect (maximum 255 characters)");
index fd1557502f6174515a3d142f8adfc8431e6ab2e9..ae0e56d89f984d5cf8b4807a9469bf4deac0b975 100644 (file)
@@ -33,14 +33,16 @@ public class UserIdentityTest {
   public ExpectedException thrown = ExpectedException.none();
 
   @Test
-  public void create_user() throws Exception {
+  public void create_user() {
     UserIdentity underTest = UserIdentity.builder()
+      .setProviderId("4321")
       .setProviderLogin("john")
       .setLogin("1234")
       .setName("John")
       .setEmail("john@email.com")
       .build();
 
+    assertThat(underTest.getProviderId()).isEqualTo("4321");
     assertThat(underTest.getProviderLogin()).isEqualTo("john");
     assertThat(underTest.getLogin()).isEqualTo("1234");
     assertThat(underTest.getName()).isEqualTo("John");
@@ -50,18 +52,36 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void create_user_without_email() throws Exception {
+  public void create_user_with_minimum_fields() {
     UserIdentity underTest = UserIdentity.builder()
       .setProviderLogin("john")
       .setLogin("1234")
       .setName("John")
       .build();
 
+    assertThat(underTest.getProviderId()).isNull();
+    assertThat(underTest.getProviderLogin()).isEqualTo("john");
+    assertThat(underTest.getLogin()).isEqualTo("1234");
+    assertThat(underTest.getName()).isEqualTo("John");
     assertThat(underTest.getEmail()).isNull();
+    assertThat(underTest.shouldSyncGroups()).isFalse();
+    assertThat(underTest.getGroups()).isEmpty();
+  }
+
+  @Test
+  public void fail_when_id_is_too_long() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("ID is too big (255 characters max)");
+    UserIdentity.builder()
+      .setProviderId(Strings.repeat("1", 256))
+      .setProviderLogin("john")
+      .setLogin("1234")
+      .setName("John")
+      .build();
   }
 
   @Test
-  public void fail_when_login_is_empty() throws Exception {
+  public void fail_when_login_is_empty() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User login must not be blank");
     UserIdentity.builder()
@@ -73,7 +93,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_login_is_too_long() throws Exception {
+  public void fail_when_login_is_too_long() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User login size is incorrect (Between 2 and 255 characters)");
     UserIdentity.builder()
@@ -85,7 +105,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_login_is_too_small() throws Exception {
+  public void fail_when_login_is_too_small() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User login size is incorrect (Between 2 and 255 characters)");
     UserIdentity.builder()
@@ -97,7 +117,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_provider_login_is_null() throws Exception {
+  public void fail_when_provider_login_is_null() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Provider login must not be blank");
     UserIdentity.builder()
@@ -108,7 +128,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_provider_login_is_empty() throws Exception {
+  public void fail_when_provider_login_is_empty() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Provider login must not be blank");
     UserIdentity.builder()
@@ -120,7 +140,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_provider_login_is_too_long() throws Exception {
+  public void fail_when_provider_login_is_too_long() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Provider login size is incorrect (maximum 255 characters)");
     UserIdentity.builder()
@@ -132,7 +152,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_name_is_null() throws Exception {
+  public void fail_when_name_is_null() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User name must not be blank");
     UserIdentity.builder()
@@ -143,7 +163,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_name_is_empty() throws Exception {
+  public void fail_when_name_is_empty() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User name must not be blank");
     UserIdentity.builder()
@@ -155,7 +175,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_name_is_loo_long() throws Exception {
+  public void fail_when_name_is_loo_long() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User name size is too big (200 characters max)");
     UserIdentity.builder()
@@ -167,7 +187,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_email_is_loo_long() throws Exception {
+  public void fail_when_email_is_loo_long() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("User email size is too big (100 characters max)");
     UserIdentity.builder()
@@ -179,7 +199,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void create_user_with_groups() throws Exception {
+  public void create_user_with_groups() {
     UserIdentity underTest = UserIdentity.builder()
       .setProviderLogin("john")
       .setLogin("1234")
@@ -193,7 +213,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_groups_are_null() throws Exception {
+  public void fail_when_groups_are_null() {
     thrown.expect(NullPointerException.class);
     thrown.expectMessage("Groups cannot be null, please don't use this method if groups should not be synchronized.");
 
@@ -206,7 +226,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_groups_contain_empty_group_name() throws Exception {
+  public void fail_when_groups_contain_empty_group_name() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Group name cannot be empty");
 
@@ -219,7 +239,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_groups_contain_only_blank_space() throws Exception {
+  public void fail_when_groups_contain_only_blank_space() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Group name cannot be empty");
 
@@ -232,7 +252,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_groups_contain_null_group_name() throws Exception {
+  public void fail_when_groups_contain_null_group_name() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Group name cannot be empty");
 
@@ -245,7 +265,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_groups_contain_anyone() throws Exception {
+  public void fail_when_groups_contain_anyone() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Anyone group cannot be used");
 
@@ -258,7 +278,7 @@ public class UserIdentityTest {
   }
 
   @Test
-  public void fail_when_groups_contain_too_long_group_name() throws Exception {
+  public void fail_when_groups_contain_too_long_group_name() {
     thrown.expect(IllegalArgumentException.class);
     thrown.expectMessage("Group name cannot be longer than 255 characters");
 
index 3e2ae82d494d941509490f96ebb347509d84db99..85ebde218233667c02ddf887e8789aee918f06f8 100644 (file)
@@ -1,3 +1,4 @@
+
 /*
  * SonarQube
  * Copyright (C) 2009-2018 SonarSource SA
@@ -17,6 +18,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import java.io.IOException;
 import org.sonar.api.config.Settings;
 import org.sonar.api.server.authentication.BaseIdentityProvider;
@@ -56,10 +58,11 @@ public class FakeBaseIdProvider implements BaseIdentityProvider {
 
     String[] userInfos = userInfoProperty.split(",");
     UserIdentity.Builder builder = UserIdentity.builder()
-      .setLogin(userInfos[0])
-      .setProviderLogin(userInfos[1])
-      .setName(userInfos[2])
-      .setEmail(userInfos[3]);
+      .setLogin(emptyToNull(userInfos[0]))
+      .setProviderId(emptyToNull(userInfos[1]))
+      .setProviderLogin(emptyToNull(userInfos[2]))
+      .setName(emptyToNull(userInfos[3]))
+      .setEmail(emptyToNull(userInfos[4]));
 
     if (settings.getBoolean(ENABLED_GROUPS_SYNC)) {
       builder.setGroups(newHashSet(settings.getStringArray(GROUPS)));
@@ -104,4 +107,15 @@ public class FakeBaseIdProvider implements BaseIdentityProvider {
     // If property is not defined, default behaviour is not always allow users to sign up
     return true;
   }
+
+  private static String emptyToNull(String s) {
+    if (s == null) {
+      return null;
+    }
+    String trim = s.trim();
+    if (trim.isEmpty()) {
+      return null;
+    }
+    return trim;
+  }
 }
index a5a3aca65ce7590bb6978aed19cb6ab84b46b0bc..af6543bf7d4f0dc55ae70351fd787e8e47b07b0d 100644 (file)
@@ -17,6 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
 import org.sonar.api.config.Settings;
 import org.sonar.api.server.authentication.Display;
 import org.sonar.api.server.authentication.OAuth2IdentityProvider;
@@ -60,10 +61,11 @@ public class FakeOAuth2IdProvider implements OAuth2IdentityProvider {
 
     String[] userInfos = userInfoProperty.split(",");
     context.authenticate(UserIdentity.builder()
-      .setLogin(userInfos[0])
-      .setProviderLogin(userInfos[1])
-      .setName(userInfos[2])
-      .setEmail(userInfos[3])
+      .setLogin(emptyToNull(userInfos[0]))
+      .setProviderId(emptyToNull(userInfos[1]))
+      .setProviderLogin(emptyToNull(userInfos[2]))
+      .setName(emptyToNull(userInfos[3]))
+      .setEmail(emptyToNull(userInfos[4]))
       .build());
     context.redirectToRequestedPage();
   }
@@ -98,7 +100,17 @@ public class FakeOAuth2IdProvider implements OAuth2IdentityProvider {
     }
     // If property is not defined, default behaviour is not always allow users to sign up
     return true;
+  }
 
+  private static String emptyToNull(String s) {
+    if (s == null) {
+      return null;
+    }
+    String trim = s.trim();
+    if (trim.isEmpty()) {
+      return null;
+    }
+    return trim;
   }
 
 }
index 7079fc7b7e43a48aa10578ee44a7cb4ae76bb19e..bcb9a3159f7c80ef5fd7314df0cd1ee5e6164d98 100644 (file)
@@ -36,7 +36,6 @@ import org.sonarqube.tests.qualityProfile.QualityProfilesEditTest;
 import org.sonarqube.tests.qualityProfile.QualityProfilesWsTest;
 import org.sonarqube.tests.rule.RulesMarkdownTest;
 import org.sonarqube.tests.rule.RulesWsTest;
-import org.sonarqube.tests.user.OrganizationIdentityProviderTest;
 
 import static util.ItUtils.newOrchestratorBuilder;
 import static util.ItUtils.pluginArtifact;
@@ -51,7 +50,6 @@ import static util.ItUtils.xooPlugin;
 @Deprecated
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
-  OrganizationIdentityProviderTest.class,
   OrganizationIssuesPageTest.class,
   OrganizationQualityProfilesUiTest.class,
   BuiltInQualityProfilesTest.class,
@@ -78,7 +76,6 @@ public class Category6Suite {
     .setServerProperty("sonar.notifications.delay", "1")
 
     .addPlugin(xooPlugin())
-    .addPlugin(pluginArtifact("base-auth-plugin"))
     .addPlugin(pluginArtifact("ui-extensions-plugin"))
 
     .setServerProperty("sonar.sonarcloud.enabled", "true")
index 9ecf5aa4e3aa780efb924b01aecabdd619c4e0ef..6e6427e72fd2c2d087621394cb9ebaadc4d38441 100644 (file)
@@ -22,69 +22,49 @@ package org.sonarqube.tests.user;
 import com.google.common.base.Joiner;
 import com.sonar.orchestrator.Orchestrator;
 import java.io.File;
-import java.util.Optional;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
-import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.RuleChain;
+import org.sonarqube.qa.util.Tester;
 import org.sonarqube.qa.util.pageobjects.Navigation;
+import org.sonarqube.ws.Users.SearchWsResponse.User;
 import org.sonarqube.ws.client.GetRequest;
-import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.usergroups.DeleteRequest;
 import org.sonarqube.ws.client.users.CreateRequest;
-import util.user.UserRule;
-import util.user.Users;
+import org.sonarqube.ws.client.users.DeactivateRequest;
+import org.sonarqube.ws.client.users.GroupsRequest;
+import org.sonarqube.ws.client.users.SearchRequest;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static util.ItUtils.newAdminWsClient;
-import static util.ItUtils.resetSettings;
-import static util.ItUtils.setServerProperty;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonarqube.ws.UserGroups.Group;
 import static util.selenium.Selenese.runSelenese;
 
-/**
- * TODO : Add missing ITs
- * - display multiple identity provider plugins (probably in another class)
- */
 public class BaseIdentityProviderTest {
 
   @ClassRule
   public static Orchestrator ORCHESTRATOR = UserSuite.ORCHESTRATOR;
 
-  private static UserRule userRule = UserRule.from(ORCHESTRATOR);
-
-  @ClassRule
-  public static RuleChain ruleChain = RuleChain.outerRule(ORCHESTRATOR).around(userRule);
-
-  static String FAKE_PROVIDER_KEY = "fake-base-id-provider";
+  @Rule
+  public Tester tester = new Tester(ORCHESTRATOR).disableOrganizations();
 
-  static String USER_LOGIN = "john";
-  static String USER_PROVIDER_ID = "fake-john";
-  static String USER_NAME = "John";
-  static String USER_EMAIL = "john@email.com";
+  private static String FAKE_PROVIDER_KEY = "fake-base-id-provider";
 
-  static String USER_NAME_UPDATED = "John Doe";
-  static String USER_EMAIL_UPDATED = "john.doe@email.com";
+  private static String USER_LOGIN = "john";
+  private static String USER_PROVIDER_ID = "ABCD";
+  private static String USER_PROVIDER_LOGIN = "fake-john";
+  private static String USER_NAME = "John";
+  private static String USER_EMAIL = "john@email.com";
 
-  static String GROUP1 = "group1";
-  static String GROUP2 = "group2";
-  static String GROUP3 = "group3";
-
-  static WsClient adminWsClient;
-
-  @BeforeClass
-  public static void setUp() {
-    ORCHESTRATOR.resetData();
-    adminWsClient = newAdminWsClient(ORCHESTRATOR);
-  }
-
-  @Before
   @After
   public void resetData() {
-    userRule.resetUsers();
-    userRule.removeGroups(GROUP1, GROUP2, GROUP3);
-    resetSettings(ORCHESTRATOR, null,
+    tester.settings().resetSettings(
       "sonar.auth.fake-base-id-provider.enabled",
       "sonar.auth.fake-base-id-provider.user",
       "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage",
@@ -96,42 +76,45 @@ public class BaseIdentityProviderTest {
   @Test
   public void create_new_user_when_authenticate() {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-
-    userRule.verifyUserDoesNotExist(USER_LOGIN);
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+    assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
 
     // First connection, user is created
     authenticateWithFakeAuthProvider();
 
-    userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL, false);
+    assertThat(tester.users().service().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+      .extracting(User::getLogin, User::getName, User::getEmail, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(USER_LOGIN, USER_NAME, USER_EMAIL, FAKE_PROVIDER_KEY, USER_PROVIDER_LOGIN, false));
   }
 
   @Test
   public void authenticate_user_through_ui() {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
 
     Navigation.create(ORCHESTRATOR).openLogin().useOAuth2().shouldBeLoggedIn();
 
-    userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL);
+    assertThat(tester.users().service().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+      .extracting(User::getLogin, User::getName, User::getEmail, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(USER_LOGIN, USER_NAME, USER_EMAIL, FAKE_PROVIDER_KEY, USER_PROVIDER_LOGIN, false));
   }
 
   @Test
   public void display_unauthorized_page_when_authentication_failed() {
     enablePlugin();
     // As this property is null, the plugin will throw an exception
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.user", null);
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", null);
 
     runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/display_unauthorized_page_when_authentication_failed.html");
 
-    userRule.verifyUserDoesNotExist(USER_LOGIN);
+    assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
   }
 
   @Test
   public void fail_when_email_already_exists() throws Exception {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    userRule.createUser("another", "Another", USER_EMAIL, "another");
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+    tester.users().generate(u -> u.setLogin("another").setName("Another").setEmail(USER_EMAIL).setPassword("another"));
 
     runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_when_email_already_exists.html");
 
@@ -143,60 +126,60 @@ public class BaseIdentityProviderTest {
   @Test
   public void fail_to_authenticate_when_not_allowed_to_sign_up() {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.allowsUsersToSignUp", "false");
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.allowsUsersToSignUp", "false");
 
     runSelenese(ORCHESTRATOR, "/user/BaseIdentityProviderTest/fail_to_authenticate_when_not_allowed_to_sign_up.html");
 
-    userRule.verifyUserDoesNotExist(USER_LOGIN);
+    assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
   }
 
   @Test
   public void update_existing_user_when_authenticate() {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
 
     // First connection, user is created
     authenticateWithFakeAuthProvider();
 
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME_UPDATED, USER_EMAIL_UPDATED);
+    String newName = "John Doe";
+    String newEmail = "john.doe@email.com";
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, newName, newEmail);
 
     // Second connection, user should be updated
     authenticateWithFakeAuthProvider();
 
-    userRule.verifyUserExists(USER_LOGIN, USER_NAME_UPDATED, USER_EMAIL_UPDATED);
+    assertThat(tester.users().service().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+      .extracting(User::getLogin, User::getName, User::getEmail, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(USER_LOGIN, newName, newEmail, FAKE_PROVIDER_KEY, USER_PROVIDER_LOGIN, false));
   }
 
   @Test
   public void reactivate_disabled_user() {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-
-    userRule.verifyUserDoesNotExist(USER_LOGIN);
-
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
     // First connection, user is created
     authenticateWithFakeAuthProvider();
-
-    Optional<Users.User> user = userRule.getUserByLogin(USER_LOGIN);
-    assertThat(user).isPresent();
-
     // Disable user
-    userRule.deactivateUsers(USER_LOGIN);
+    tester.users().service().deactivate(new DeactivateRequest().setLogin(USER_LOGIN));
 
     // Second connection, user is reactivated
     authenticateWithFakeAuthProvider();
-    userRule.verifyUserExists(USER_LOGIN, USER_NAME, USER_EMAIL);
+
+    assertThat(tester.users().service().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+      .extracting(User::getLogin, User::getName, User::getEmail, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(USER_LOGIN, USER_NAME, USER_EMAIL, FAKE_PROVIDER_KEY, USER_PROVIDER_LOGIN, false));
   }
 
   @Test
   public void not_authenticate_when_plugin_is_disabled() {
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabled", "false");
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabled", "false");
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
 
     authenticateWithFakeAuthProvider();
 
     // User is not created as nothing plugin is disabled
-    userRule.verifyUserDoesNotExist(USER_LOGIN);
+    assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
 
     // TODO Add Selenium test to check login form
   }
@@ -204,8 +187,8 @@ public class BaseIdentityProviderTest {
   @Test
   public void display_message_in_ui_but_not_in_log_when_unauthorized_exception() throws Exception {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true");
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "true");
 
     runSelenese(ORCHESTRATOR,
       "/user/BaseIdentityProviderTest/display_message_in_ui_but_not_in_log_when_unauthorized_exception.html");
@@ -214,111 +197,187 @@ public class BaseIdentityProviderTest {
     assertThat(FileUtils.readFileToString(logFile)).doesNotContain("A functional error has happened");
     assertThat(FileUtils.readFileToString(logFile)).doesNotContain("UnauthorizedException");
 
-    userRule.verifyUserDoesNotExist(USER_LOGIN);
+    assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
   }
 
   @Test
   public void synchronize_groups_for_new_user() {
     enablePlugin();
-    userRule.createGroup(GROUP1);
-    userRule.createGroup(GROUP2);
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    // Group3 doesn't exist in DB, user won't belong to this group
-    setGroupsReturnedByAuthPlugin(GROUP1, GROUP2, GROUP3);
-
-    authenticateWithFakeAuthProvider();
-
-    userRule.verifyUserGroupMembership(USER_LOGIN, GROUP1, GROUP2, "sonar-users");
+    Group group1 = tester.groups().generate();
+    Group group2 = tester.groups().generate();
+    Group group3 = tester.groups().generate();
+    try {
+      setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+      setGroupsReturnedByAuthPlugin(group1.getName(), group2.getName(), "Another group");
+
+      authenticateWithFakeAuthProvider();
+
+      assertThat(tester.users().service().groups(new GroupsRequest().setLogin(USER_LOGIN)).getGroupsList())
+        .extracting(org.sonarqube.ws.Users.GroupsWsResponse.Group::getName)
+        .containsExactlyInAnyOrder(group1.getName(), group2.getName(), "sonar-users");
+    } finally {
+      deleteGroups(group1, group2, group3);
+    }
   }
 
   @Test
   public void synchronize_groups_for_existing_user() {
     enablePlugin();
-    userRule.createGroup(GROUP1);
-    userRule.createGroup(GROUP2);
-    userRule.createGroup(GROUP3);
-    userRule.createUser(USER_LOGIN, "password");
-    userRule.associateGroupsToUser(USER_LOGIN, GROUP1, GROUP2);
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    // Group1 is not returned by the plugin, user won't belong anymore to this group
-    setGroupsReturnedByAuthPlugin(GROUP2, GROUP3);
-
-    authenticateWithFakeAuthProvider();
-
-    userRule.verifyUserGroupMembership(USER_LOGIN, GROUP2, GROUP3, "sonar-users");
+    Group group1 = tester.groups().generate();
+    Group group2 = tester.groups().generate();
+    Group group3 = tester.groups().generate();
+    try {
+      tester.users().generate(u -> u.setLogin(USER_LOGIN).setPassword("password"));
+      tester.groups().addMemberToGroups(tester.organizations().getDefaultOrganization(), USER_LOGIN, group1.getName(), group2.getName());
+      setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+      // Group1 is not returned by the plugin, user won't belong anymore to this group
+      setGroupsReturnedByAuthPlugin(group2.getName(), group3.getName());
+
+      authenticateWithFakeAuthProvider();
+
+      assertThat(tester.users().service().groups(new GroupsRequest().setLogin(USER_LOGIN)).getGroupsList())
+        .extracting(org.sonarqube.ws.Users.GroupsWsResponse.Group::getName)
+        .containsExactlyInAnyOrder(group2.getName(), group3.getName(), "sonar-users");
+    } finally {
+      deleteGroups(group1, group2, group3);
+    }
   }
 
   @Test
   public void remove_user_groups_when_groups_provided_by_plugin_are_empty() {
     enablePlugin();
-    userRule.createGroup(GROUP1);
-    userRule.createUser(USER_LOGIN, "password");
-    userRule.associateGroupsToUser(USER_LOGIN, GROUP1);
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    // No group is returned by the plugin
-    setGroupsReturnedByAuthPlugin();
-
-    authenticateWithFakeAuthProvider();
-
-    // User is not member to any group
-    userRule.verifyUserGroupMembership(USER_LOGIN, "sonar-users");
+    Group group = tester.groups().generate();
+    try {
+      tester.users().generate(u -> u.setLogin(USER_LOGIN).setPassword("password"));
+      tester.groups().addMemberToGroups(tester.organizations().getDefaultOrganization(), USER_LOGIN, group.getName());
+      setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
+      // No group is returned by the plugin
+      setGroupsReturnedByAuthPlugin();
+
+      authenticateWithFakeAuthProvider();
+
+      // User is not member to any group
+      assertThat(tester.users().service().groups(new GroupsRequest().setLogin(USER_LOGIN)).getGroupsList())
+        .extracting(org.sonarqube.ws.Users.GroupsWsResponse.Group::getName)
+        .containsExactlyInAnyOrder("sonar-users");
+    } finally {
+      deleteGroups(group);
+    }
   }
 
   @Test
   public void allow_user_login_with_2_characters() {
     enablePlugin();
     String login = "jo";
-    setUserCreatedByAuthPlugin(login, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
-    userRule.verifyUserDoesNotExist(login);
+    setUserCreatedByAuthPlugin(login, login, login, USER_NAME, USER_EMAIL);
+    assertThat(tester.users().getByLogin(login)).isNotPresent();
 
     // First connection, user is created
     authenticateWithFakeAuthProvider();
 
-    userRule.verifyUserExists(login, USER_NAME, USER_EMAIL, false);
+    assertThat(tester.users().getByLogin(login)).isPresent();
   }
 
   @Test
   public void provision_user_before_authentication() {
     enablePlugin();
-    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_NAME, USER_EMAIL);
+    setUserCreatedByAuthPlugin(USER_LOGIN, USER_PROVIDER_ID, USER_PROVIDER_LOGIN, USER_NAME, USER_EMAIL);
 
     // Provision none local user in database
-    newAdminWsClient(ORCHESTRATOR).users().create(new CreateRequest()
+    tester.users().service().create(new CreateRequest()
       .setLogin(USER_LOGIN)
       .setName(USER_NAME)
       .setEmail(USER_EMAIL)
       .setLocal("false"));
-    assertThat(userRule.getUserByLogin(USER_LOGIN).get())
-      .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider)
-      .containsOnly(false, USER_LOGIN, "sonarqube");
+    assertThat(tester.users().service().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+      .extracting(User::getLogin, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(USER_LOGIN, "sonarqube", USER_LOGIN, false));
 
     // Authenticate with external system -> It will update external provider info
     authenticateWithFakeAuthProvider();
 
-    assertThat(userRule.getUserByLogin(USER_LOGIN).get())
-      .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider)
-      .containsOnly(false, USER_PROVIDER_ID, FAKE_PROVIDER_KEY);
+    assertThat(tester.users().service().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+      .extracting(User::getLogin, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(USER_LOGIN, FAKE_PROVIDER_KEY, USER_PROVIDER_LOGIN, false));
   }
 
-  private static void enablePlugin() {
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabled", "true");
+  @Test
+  public void update_login() {
+    enablePlugin();
+    String oldLogin = "login_to_update@base";
+    setUserCreatedByAuthPlugin(oldLogin, "login_to_update_id", "old_provider_login", USER_NAME, USER_EMAIL);
+    assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
+    authenticateWithFakeAuthProvider();
+
+    // Login is updated
+    String newLogin = "new_login@base";
+    setUserCreatedByAuthPlugin(newLogin, "login_to_update_id", "new_provider_login", USER_NAME, USER_EMAIL);
+    authenticateWithFakeAuthProvider();
+
+    assertThat(tester.users().getByLogin(oldLogin)).isNotPresent();
+    assertThat(tester.users().service().search(new SearchRequest().setQ(newLogin)).getUsersList())
+      .extracting(User::getLogin, User::getName, User::getEmail, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
+      .containsExactlyInAnyOrder(tuple(newLogin, USER_NAME, USER_EMAIL, FAKE_PROVIDER_KEY, "new_provider_login", false));
+    // Check that searching for old login return nothing
+    assertThat(tester.users().service().search(new SearchRequest().setQ(oldLogin)).getPaging().getTotal()).isZero();
   }
 
-  private static void setUserCreatedByAuthPlugin(String login, String providerId, String name, String email) {
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.user", login + "," + providerId + "," + name + "," + email);
+  @Test
+  public void authenticate_with_external_id_null() {
+    enablePlugin();
+    String login = "no_external_id_login";
+    setUserCreatedByAuthPlugin(login, null, "no_external_id_provider_login", USER_NAME, USER_EMAIL);
+
+    // First authentication
+    authenticateWithFakeAuthProvider();
+    assertThat(tester.users().getByLogin(login)).isPresent();
+
+    // De-activate and re-authenticate to check everything is ok
+    tester.users().service().deactivate(new DeactivateRequest().setLogin(login));
+    authenticateWithFakeAuthProvider();
+    assertThat(tester.users().getByLogin(login)).isPresent();
   }
 
-  private static void setGroupsReturnedByAuthPlugin(String... groups) {
-    setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.enabledGroupsSync", "true");
+  @Test
+  public void update_external_id() {
+    enablePlugin();
+    String login = "update_external_id_login";
+    setUserCreatedByAuthPlugin(login, "old_external_id", login, USER_NAME, USER_EMAIL);
+    authenticateWithFakeAuthProvider();
+    assertThat(tester.users().getByLogin(login)).isPresent();
+
+    setUserCreatedByAuthPlugin(login, "new_external_id", login, USER_NAME, USER_EMAIL);
+    authenticateWithFakeAuthProvider();
+    assertThat(tester.users().getByLogin(login)).isPresent();
+  }
+
+  private void enablePlugin() {
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabled", "true");
+  }
+
+  private void setUserCreatedByAuthPlugin(String login, @Nullable String providerId, String providerLogin, String name, String email) {
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login + "," + (providerId == null ? "" : providerId) + "," + providerLogin + "," + name + "," + email);
+  }
+
+  private void setGroupsReturnedByAuthPlugin(String... groups) {
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabledGroupsSync", "true");
     if (groups.length > 0) {
-      setServerProperty(ORCHESTRATOR, "sonar.auth.fake-base-id-provider.groups", Joiner.on(",").join(groups));
+      tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.groups", Joiner.on(",").join(groups));
     }
   }
 
-  private static void authenticateWithFakeAuthProvider() {
-    adminWsClient.wsConnector().call(
+  private void authenticateWithFakeAuthProvider() {
+    tester.wsClient().wsConnector().call(
       new GetRequest("/sessions/init/" + FAKE_PROVIDER_KEY))
       .failIfNotSuccessful();
   }
 
+  private void deleteGroups(Group... groups) {
+    List<String> allGroups = tester.wsClient().userGroups().search(new org.sonarqube.ws.client.usergroups.SearchRequest()).getGroupsList().stream().map(Group::getName)
+      .collect(Collectors.toList());
+    Arrays.stream(groups)
+      .filter(g -> allGroups.contains(g.getName()))
+      .forEach(g -> tester.wsClient().userGroups().delete(new DeleteRequest().setName(g.getName())));
+  }
+
 }
index 103b634ccd7b225100a27834fbd386c4e49f11e1..90933ed35c7cff1e58e0873f27664beba3ac6469 100644 (file)
@@ -54,7 +54,8 @@ public class OAuth2IdentityProviderTest {
   private static String FAKE_PROVIDER_KEY = "fake-oauth2-id-provider";
 
   private static String USER_LOGIN = "john";
-  private static String USER_PROVIDER_ID = "fake-john";
+  private static String USER_PROVIDER_ID = "ABCD";
+  private static String USER_PROVIDER_LOGIN = "fake-john";
   private static String USER_NAME = "John";
   private static String USER_EMAIL = "john@email.com";
 
@@ -225,10 +226,26 @@ public class OAuth2IdentityProviderTest {
 
     user = tester.users().getByLogin(USER_LOGIN).get();
     assertThat(user.getLocal()).isFalse();
-    assertThat(user.getExternalIdentity()).isEqualTo(USER_PROVIDER_ID);
+    assertThat(user.getExternalIdentity()).isEqualTo(USER_PROVIDER_LOGIN);
     assertThat(user.getExternalProvider()).isEqualTo(FAKE_PROVIDER_KEY);
   }
 
+  @Test
+  public void update_login() {
+    simulateRedirectionToCallback();
+    enablePlugin();
+
+    String oldLogin = "login_to_update@oauth2";
+    tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", oldLogin + "," + "login_to_update_id" + "," + "old_provider_login" + "," + USER_NAME + "," + USER_EMAIL);
+    authenticateWithFakeAuthProvider();
+
+    String newLogin = "new_logine@oauth2";
+    tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", newLogin + "," + "login_to_update_id" + "," + "new_provider_login" + "," + USER_NAME + "," + USER_EMAIL);
+    authenticateWithFakeAuthProvider();
+
+    verifyUser(newLogin, USER_NAME, USER_EMAIL);
+  }
+
   private void verifyUser(String login, String name, String email) {
     User user = tester.users().getByLogin(login).orElseThrow(IllegalStateException::new);
     assertThat(user.getLogin()).isEqualTo(login);
@@ -255,7 +272,7 @@ public class OAuth2IdentityProviderTest {
   private void enablePlugin() {
     tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.enabled", "true");
     tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.url", fakeServerAuthProviderUrl);
-    tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", USER_LOGIN + "," + USER_PROVIDER_ID + "," + USER_NAME + "," + USER_EMAIL);
+    tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", USER_LOGIN + "," + USER_PROVIDER_ID + "," + USER_PROVIDER_LOGIN + "," + USER_NAME + "," + USER_EMAIL);
   }
 
   private void assertThatUserDoesNotExist(String login) {
index 5e2840ca490d0ce3529487aefc78379b899165b7..a05dbd0c90a20c7710cad4b85b27841d399f4a99 100644 (file)
@@ -27,7 +27,6 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonarqube.qa.util.Tester;
-import org.sonarqube.tests.Category6Suite;
 import org.sonarqube.ws.UserGroups.Group;
 import org.sonarqube.ws.Users.CreateWsResponse.User;
 import org.sonarqube.ws.client.GetRequest;
@@ -36,7 +35,7 @@ import org.sonarqube.ws.client.organizations.AddMemberRequest;
 public class OrganizationIdentityProviderTest {
 
   @ClassRule
-  public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR;
+  public static Orchestrator orchestrator = SonarCloudUserSuite.ORCHESTRATOR;
 
   @Rule
   public Tester tester = new Tester(orchestrator);
@@ -49,14 +48,17 @@ public class OrganizationIdentityProviderTest {
 
   @After
   public void tearDown() {
-    tester.settings().resetSettings("sonar.auth.fake-base-id-provider.enabled", "sonar.auth.fake-base-id-provider.user",
-      "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage", "sonar.auth.fake-base-id-provider.enabledGroupsSync", "sonar.auth.fake-base-id-provider.groups",
+    tester.settings().resetSettings("sonar.auth.fake-base-id-provider.enabled",
+      "sonar.auth.fake-base-id-provider.user",
+      "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage",
+      "sonar.auth.fake-base-id-provider.enabledGroupsSync",
+      "sonar.auth.fake-base-id-provider.groups",
       "sonar.auth.fake-base-id-provider.allowsUsersToSignUp");
   }
 
   @Test
-  public void default_group_is_not_added_for_new_user_when_organizations_are_enabled() {
-    Group group = tester.groups().generate(null);
+  public void default_group_is_not_added_for_new_user() {
+    Group group = tester.groups().generate();
     enableUserCreationByAuthPlugin("aLogin");
     setGroupsReturnedByAuthPlugin(group.getName());
 
@@ -67,8 +69,8 @@ public class OrganizationIdentityProviderTest {
   }
 
   @Test
-  public void default_group_is_not_sync_for_existing_user_when_organizations_are_enabled() {
-    Group group = tester.groups().generate(null);
+  public void default_group_is_not_sync_for_existing_user() {
+    Group group = tester.groups().generate();
     User user = tester.users().generate();
     enableUserCreationByAuthPlugin(user.getLogin());
     setGroupsReturnedByAuthPlugin(group.getName());
@@ -80,8 +82,8 @@ public class OrganizationIdentityProviderTest {
   }
 
   @Test
-  public void remove_default_group_when_organizations_are_enabled() {
-    Group group = tester.groups().generate(null);
+  public void remove_default_group() {
+    Group group = tester.groups().generate();
     User user = tester.users().generate();
     // Add user as member of default organization
     tester.wsClient().organizations().addMember(new AddMemberRequest().setOrganization("default-organization").setLogin(user.getLogin()));
@@ -97,7 +99,7 @@ public class OrganizationIdentityProviderTest {
   }
 
   private void enableUserCreationByAuthPlugin(String login) {
-    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login + ",fake-john,John,john@email.com");
+    tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login +"," + login + ",fake-" + login + ",John,john@email.com");
   }
 
   private void setGroupsReturnedByAuthPlugin(String... groups) {
index c0f14d8e6a34e5f1cbb16c8d6b674482f885a1ea..c733ffab7e64a1a69cd4979d3134724d36e6cf9a 100644 (file)
@@ -41,15 +41,17 @@ import org.sonarqube.qa.util.Tester;
 import org.sonarqube.qa.util.pageobjects.Navigation;
 import org.sonarqube.qa.util.pageobjects.SystemInfoPage;
 import org.sonarqube.qa.util.pageobjects.UsersManagementPage;
+import org.sonarqube.ws.Users.SearchWsResponse.User;
 import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.WsResponse;
 import org.sonarqube.ws.client.users.CreateRequest;
+import org.sonarqube.ws.client.users.SearchRequest;
 import util.user.UserRule;
-import util.user.Users;
 
 import static java.net.HttpURLConnection.HTTP_OK;
 import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
 import static org.junit.Assert.fail;
 import static util.ItUtils.newOrchestratorBuilder;
 import static util.ItUtils.newUserWsClient;
@@ -123,6 +125,7 @@ public class RealmAuthenticationTest {
     updateUsersInExtAuth(users);
     // Then
     verifyAuthenticationIsOk(username, password);
+    verifyUser();
 
     // with external details and groups
     runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html");
@@ -150,6 +153,7 @@ public class RealmAuthenticationTest {
     updateUsersInExtAuth(users);
     // Then
     verifyAuthenticationIsOk(username, password);
+    verifyUser();
 
     // with external details and groups
     // TODO replace by WS ? Or with new Selenese utils
@@ -228,6 +232,7 @@ public class RealmAuthenticationTest {
     updateUsersInExtAuth(users);
     // Then
     verifyAuthenticationIsOk(username, password);
+    verifyUser();
     verifyAuthenticationIsNotOk(username, "wrong");
   }
 
@@ -257,6 +262,7 @@ public class RealmAuthenticationTest {
     updateUsersInExtAuth(users);
     // check that the deleted/deactivated user "tester" has been reactivated and can now log in
     verifyAuthenticationIsOk(login, password);
+    verifyUser();
   }
 
   /**
@@ -300,6 +306,7 @@ public class RealmAuthenticationTest {
     updateUsersInExtAuth(users);
 
     verifyAuthenticationIsOk(login, password);
+    verifyUser();
     verifyAuthenticationIsNotOk("wrong", password);
     verifyAuthenticationIsNotOk(login, "wrong");
     verifyAuthenticationIsNotOk(login, null);
@@ -339,9 +346,7 @@ public class RealmAuthenticationTest {
       USER_LOGIN + ".email", "tester@example.org"));
 
     verifyAuthenticationIsOk(USER_LOGIN, "123");
-    assertThat(userRule.getUserByLogin(USER_LOGIN).get())
-      .extracting(Users.User::isLocal, Users.User::getExternalIdentity, Users.User::getExternalProvider)
-      .containsOnly(false, USER_LOGIN, "sonarqube");
+    verifyUser();
   }
 
   @Test
@@ -414,6 +419,12 @@ public class RealmAuthenticationTest {
     assertThat(checkAuthenticationWithWebService(login, password).code()).isEqualTo(HTTP_UNAUTHORIZED);
   }
 
+  private void verifyUser(){
+    assertThat(tester.wsClient().users().search(new SearchRequest().setQ(USER_LOGIN)).getUsersList())
+    .extracting(User::getLogin, User::getExternalIdentity, User::getExternalProvider, User::getLocal)
+    .containsExactlyInAnyOrder(tuple(USER_LOGIN, USER_LOGIN, "sonarqube", false));
+  }
+
   private WsResponse checkAuthenticationWithWebService(String login, String password) {
     // Call any WS
     return newUserWsClient(orchestrator, login, password).wsConnector().call(new GetRequest("api/rules/search"));
index b0a677fe7aa65ba60e3be255ffec67663ee72a41..731afead4c9f449a07f674293f228d9ccf2472cc 100644 (file)
@@ -26,10 +26,12 @@ import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
 import static util.ItUtils.newOrchestratorBuilder;
+import static util.ItUtils.pluginArtifact;
 import static util.ItUtils.xooPlugin;
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
+  OrganizationIdentityProviderTest.class,
   SonarCloudHomepageTest.class,
   SonarCloudNotificationsWsTest.class
 })
@@ -39,6 +41,9 @@ public class SonarCloudUserSuite {
   public static final Orchestrator ORCHESTRATOR = newOrchestratorBuilder()
     .addPlugin(xooPlugin())
 
+    // Used by OrganizationIdentityProviderTest
+    .addPlugin(pluginArtifact("base-auth-plugin"))
+
     .setServerProperty("sonar.sonarcloud.enabled", "true")
 
     // reduce memory for Elasticsearch
index 072d9d0bfd319bce286b4b71c6faca378fc39ad1..006145e5ab7418b2bd372ae85f9447a5d9ec70bb 100644 (file)
@@ -90,7 +90,7 @@ public class UsersPageTest {
     String login = randomAlphabetic(10);
     String group = randomAlphabetic(10);
     tester.users().generate(u -> u.setLogin(login).setPassword(login));
-    tester.groups().generate(null, g -> g.setName(group));
+    tester.groups().generate(g -> g.setName(group));
     tester.groups().addMemberToGroups(tester.organizations().getDefaultOrganization(), login, group);
 
     List<Users.GroupsWsResponse.Group> result = tester.as(adminUser.getLogin()).wsClient().users().groups(new GroupsRequest().setLogin(login)).getGroupsList();