Browse Source

SONAR-10599 Synchronize login during authentication

tags/7.5
Julien Lancelot 6 years ago
parent
commit
a124513d1f
100 changed files with 3006 additions and 1522 deletions
  1. 1
    1
      server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql
  2. 7
    4
      server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
  3. 9
    10
      server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java
  4. 2
    2
      server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberMapper.java
  5. 4
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
  6. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java
  7. 22
    4
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java
  8. 15
    5
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java
  9. 8
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java
  10. 7
    7
      server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMemberMapper.xml
  11. 10
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
  12. 34
    7
      server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml
  13. 3
    3
      server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDbTester.java
  14. 11
    11
      server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationMemberDaoTest.java
  15. 155
    73
      server/sonar-db-dao/src/test/java/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest.java
  16. 87
    73
      server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java
  17. 3
    5
      server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java
  18. 60
    23
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
  19. 12
    2
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java
  20. 7
    7
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java
  21. 0
    65
      server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml
  22. 0
    62
      server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions.xml
  23. 0
    58
      server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml
  24. 46
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsers.java
  25. 62
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsers.java
  26. 7
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java
  27. 72
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullable.java
  28. 53
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsers.java
  29. 46
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsers.java
  30. 75
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsers.java
  31. 55
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest.java
  32. 56
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest.java
  33. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java
  34. 62
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest.java
  35. 95
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest.java
  36. 58
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest.java
  37. 119
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest.java
  38. 22
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest/users.sql
  39. 23
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest/users.sql
  40. 23
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest/users.sql
  41. 23
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest/users.sql
  42. 23
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest/users.sql
  43. 23
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest/users.sql
  44. 5
    0
      server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java
  45. 1
    1
      server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/UserTester.java
  46. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/authentication/EmailAlreadyExistsException.java
  47. 54
    30
      server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java
  48. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java
  49. 2
    3
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java
  50. 8
    11
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java
  51. 4
    7
      server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationFactory.java
  52. 4
    2
      server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java
  53. 10
    2
      server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java
  54. 12
    11
      server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java
  55. 59
    34
      server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
  56. 10
    1
      server/sonar-server/src/main/java/org/sonar/server/user/index/UserDoc.java
  57. 0
    15
      server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java
  58. 2
    0
      server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexDefinition.java
  59. 20
    22
      server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexer.java
  60. 17
    6
      server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java
  61. 11
    4
      server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java
  62. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java
  63. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java
  64. 21
    9
      server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java
  65. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java
  66. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java
  67. 3
    4
      server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java
  68. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java
  69. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java
  70. 94
    12
      server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java
  71. 20
    31
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ScmAccountToUserLoaderTest.java
  72. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java
  73. 12
    33
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java
  74. 1
    2
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java
  75. 12
    30
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java
  76. 137
    146
      server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java
  77. 2
    4
      server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
  78. 1
    6
      server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java
  79. 2
    10
      server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/MetricsActionTest.java
  80. 2
    9
      server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/SearchActionTest.java
  81. 1
    5
      server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java
  82. 11
    3
      server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java
  83. 26
    2
      server/sonar-server/src/test/java/org/sonar/server/organization/ws/RemoveMemberActionTest.java
  84. 20
    37
      server/sonar-server/src/test/java/org/sonar/server/user/DeprecatedUserFinderTest.java
  85. 15
    6
      server/sonar-server/src/test/java/org/sonar/server/user/ExternalIdentityTest.java
  86. 4
    3
      server/sonar-server/src/test/java/org/sonar/server/user/NewUserTest.java
  87. 158
    92
      server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java
  88. 70
    257
      server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java
  89. 339
    0
      server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java
  90. 192
    109
      server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java
  91. 3
    27
      server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexTest.java
  92. 57
    10
      server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexerTest.java
  93. 71
    63
      server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java
  94. 34
    13
      server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
  95. 3
    3
      server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java
  96. 12
    4
      server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
  97. 7
    9
      server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java
  98. 14
    1
      server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java
  99. 30
    0
      sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/UserIdentity.java
  100. 0
    0
      sonar-plugin-api/src/test/java/org/sonar/api/server/authentication/UserIdentityTest.java

+ 1
- 1
server/sonar-db-core/src/main/resources/org/sonar/db/version/rows-h2.sql View 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');

+ 7
- 4
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl View 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");



+ 9
- 10
server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberDao.java View 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")));
}
}

+ 2
- 2
server/sonar-db-dao/src/main/java/org/sonar/db/organization/OrganizationMemberMapper.java View 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();


+ 4
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java View 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>

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java View 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);


+ 22
- 4
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDao.java View 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));
}


+ 15
- 5
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserDto.java View 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;
}


+ 8
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserMapper.java View 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);

/**

+ 7
- 7
server/sonar-db-dao/src/main/resources/org/sonar/db/organization/OrganizationMemberMapper.xml View 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}
@@ -37,17 +37,17 @@
</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>

+ 10
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml View File

@@ -179,6 +179,16 @@
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
(

+ 34
- 7
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserMapper.xml View 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",
@@ -25,6 +26,13 @@
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"/>
@@ -66,6 +74,16 @@
</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"/>
@@ -117,6 +135,13 @@
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)
@@ -133,8 +158,6 @@
active = ${_false},
email = null,
scm_accounts = null,
external_identity = null,
external_identity_provider = null,
salt = null,
crypted_password = null,
updated_at = #{now, jdbcType=BIGINT}
@@ -178,7 +201,8 @@
email,
active,
scm_accounts,
external_identity,
external_id,
external_login,
external_identity_provider,
user_local,
salt,
@@ -197,7 +221,8 @@
#{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},
@@ -214,11 +239,13 @@

<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},
@@ -229,7 +256,7 @@
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>

+ 3
- 3
server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationDbTester.java View 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();
}


+ 11
- 11
server/sonar-db-dao/src/test/java/org/sonar/db/organization/OrganizationMemberDaoTest.java View 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

+ 155
- 73
server/sonar-db-dao/src/test/java/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest.java View File

@@ -20,135 +20,217 @@
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();
}
}

+ 87
- 73
server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java View 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);
}

}

+ 3
- 5
server/sonar-db-dao/src/test/java/org/sonar/db/user/GroupMembershipDaoTest.java View 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");

+ 60
- 23
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java View 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

+ 12
- 2
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java View 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);
}


+ 7
- 7
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java View 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);

+ 0
- 65
server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/select_only_enable_users.xml View File

@@ -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>

+ 0
- 62
server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions.xml View File

@@ -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>

+ 0
- 58
server/sonar-db-dao/src/test/resources/org/sonar/db/permission/template/UserWithPermissionTemplateDaoTest/users_with_permissions_should_be_sorted_by_user_name.xml View File

@@ -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>

+ 46
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsers.java View File

@@ -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());
}

}

+ 62
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsers.java View File

@@ -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();
}

}

+ 7
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72.java View 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)

;
}
}

+ 72
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullable.java View File

@@ -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();
}

}

+ 53
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsers.java View File

@@ -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;
});
}
}

+ 46
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsers.java View File

@@ -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());
}

}

+ 75
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsers.java View File

@@ -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;
});
}

}

+ 55
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest.java View File

@@ -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();
}

}

+ 56
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest.java View File

@@ -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();
}

}

+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/DbVersion72Test.java View File

@@ -34,7 +34,7 @@ public class DbVersion72Test {

@Test
public void verify_migration_count() {
verifyMigrationCount(underTest, 9);
verifyMigrationCount(underTest, 15);
}

}

+ 62
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest.java View File

@@ -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");
}

}

+ 95
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest.java View File

@@ -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);
}
}

+ 58
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest.java View File

@@ -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();
}

}

+ 119
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest.java View File

@@ -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);
}

}

+ 22
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddExternalIdToUsersTest/users.sql View File

@@ -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");

+ 23
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/AddUniqueIndexesOnUsersTest/users.sql View File

@@ -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");

+ 23
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/MakeSomeColumnsOfUsersNotNullableTest/users.sql View File

@@ -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");

+ 23
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/PopulateExternalIdOnUsersTest/users.sql View File

@@ -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");

+ 23
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/RenameExternalIdentityToExternalLoginOnUsersTest/users.sql View File

@@ -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");

+ 23
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v72/UpdateNullValuesFromExternalColumnsAndLoginOfUsersTest/users.sql View File

@@ -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");

+ 5
- 0
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/GroupTester.java View 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)

+ 1
- 1
server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/UserTester.java View 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();
});
}


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/authentication/EmailAlreadyExistsException.java View 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()));
}
}

+ 54
- 30
server/sonar-server/src/main/java/org/sonar/server/authentication/UserIdentityAuthenticator.java View 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[] {});
}

}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStep.java View 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))

+ 2
- 3
server/sonar-server/src/main/java/org/sonar/server/issue/notification/MyNewIssuesNotification.java View 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) {

+ 8
- 11
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotification.java View 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++;

+ 4
- 7
server/sonar-server/src/main/java/org/sonar/server/issue/notification/NewIssuesNotificationFactory.java View 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);
}
}

+ 4
- 2
server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java View 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) {

+ 10
- 2
server/sonar-server/src/main/java/org/sonar/server/user/ExternalIdentity.java View 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;
}

+ 12
- 11
server/sonar-server/src/main/java/org/sonar/server/user/UpdateUser.java View 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);
}
}

+ 59
- 34
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java View 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);

+ 10
- 1
server/sonar-server/src/main/java/org/sonar/server/user/index/UserDoc.java View 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;

+ 0
- 15
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java View 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.

+ 2
- 0
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexDefinition.java View 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();

+ 20
- 22
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndexer.java View 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())

+ 17
- 6
server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java View 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) {

+ 11
- 4
server/sonar-server/src/main/java/org/sonar/server/user/ws/CreateAction.java View 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 -> {
}));
}
}


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java View 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();
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/user/ws/SearchAction.java View 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(),

+ 21
- 9
server/sonar-server/src/main/java/org/sonar/server/user/ws/UpdateAction.java View 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();
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/user/ws/UserJsonWriter.java View 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);

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/authentication/InitFilterTest.java View 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);

+ 3
- 4
server/sonar-server/src/test/java/org/sonar/server/authentication/LocalAuthenticationTest.java View 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");

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java View 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);

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java View 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);
}

+ 94
- 12
server/sonar-server/src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java View 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();
}

+ 20
- 31
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/ScmAccountToUserLoaderTest.java View 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);

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java View 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);
}


+ 12
- 33
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesEmailTemplateTest.java View 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() {

+ 1
- 2
server/sonar-server/src/test/java/org/sonar/server/issue/notification/MyNewIssuesNotificationTest.java View 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() {

+ 12
- 30
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesEmailTemplateTest.java View 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() {

+ 137
- 146
server/sonar-server/src/test/java/org/sonar/server/issue/notification/NewIssuesNotificationTest.java View File

@@ -19,40 +19,27 @@
*/
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);
}
}

+ 2
- 4
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java View 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();

+ 1
- 6
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java View 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);

+ 2
- 10
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/MetricsActionTest.java View 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

+ 2
- 9
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/SearchActionTest.java View 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

+ 1
- 5
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java View 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)

+ 11
- 3
server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java View 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);

+ 26
- 2
server/sonar-server/src/test/java/org/sonar/server/organization/ws/RemoveMemberActionTest.java View 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) {

+ 20
- 37
server/sonar-server/src/test/java/org/sonar/server/user/DeprecatedUserFinderTest.java View 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();
}

}

+ 15
- 6
server/sonar-server/src/test/java/org/sonar/server/user/ExternalIdentityTest.java View 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");
}

}

+ 4
- 3
server/sonar-server/src/test/java/org/sonar/server/user/NewUserTest.java View 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();
}
}

+ 158
- 92
server/sonar-server/src/test/java/org/sonar/server/user/ServerUserSessionTest.java View 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");

+ 70
- 257
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterCreateTest.java View 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());
}

+ 339
- 0
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterReactivateTest.java View File

@@ -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());
}

}

+ 192
- 109
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterUpdateTest.java View 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());
}

+ 3
- 27
server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexTest.java View 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)

+ 57
- 10
server/sonar-server/src/test/java/org/sonar/server/user/index/UserIndexerTest.java View 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);
}
}

+ 71
- 63
server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java View 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"));
}
}

+ 34
- 13
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java View 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)

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java View 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)

+ 12
- 4
server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java View 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

+ 7
- 9
server/sonar-server/src/test/java/org/sonar/server/user/ws/SearchActionTest.java View 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);


+ 14
- 1
server/sonar-server/src/test/java/org/sonar/server/user/ws/UpdateActionTest.java View 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);

+ 30
- 0
sonar-plugin-api/src/main/java/org/sonar/api/server/authentication/UserIdentity.java View 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)");

+ 0
- 0
sonar-plugin-api/src/test/java/org/sonar/api/server/authentication/UserIdentityTest.java View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save