* SONAR-10598 Refactor UserIdentityAuthenticator#authenticate to use a ParameterObject
* SONAR-10598 Redirect user when login is updated and update personal org
* SONAR-10598 Improve update of personal organization key
* SONAR-10598 Improve IT stability related to generation of provider ID
* SONAR-10598 Add USERS#ORGANIZATION_UUID
* SONAR-10598 Replace usage of Organizaions#UserId by Users#OrganizationUuid
"URL" VARCHAR(256),
"AVATAR_URL" VARCHAR(256),
"GUARDED" BOOLEAN NOT NULL,
- "USER_ID" INTEGER,
"DEFAULT_PERM_TEMPLATE_PROJECT" VARCHAR(40),
"DEFAULT_PERM_TEMPLATE_VIEW" VARCHAR(40),
"DEFAULT_GROUP_ID" INTEGER,
"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)
+ "HOMEPAGE_PARAMETER" VARCHAR(40),
+ "ORGANIZATION_UUID" VARCHAR(40),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT
);
CREATE UNIQUE INDEX "USERS_UUID" ON "USERS" ("UUID");
CREATE UNIQUE INDEX "USERS_LOGIN" ON "USERS" ("LOGIN");
return this;
}
- @CheckForNull
- public Integer getUserId() {
- return userId;
- }
-
- public OrganizationDto setUserId(@Nullable Integer userId) {
- this.userId = userId;
- return this;
- }
-
@CheckForNull
public Integer getDefaultGroupId() {
return defaultGroupId;
private String salt;
// Hash method used to generate cryptedPassword, my be null in case of external authentication
private String hashMethod;
- private Long createdAt;
- private Long updatedAt;
private String homepageType;
private String homepageParameter;
private boolean local = true;
private boolean root = false;
private boolean onboarded = false;
+ private String organizationUuid;
+ private Long createdAt;
+ private Long updatedAt;
public String getUuid() {
return uuid;
return this;
}
- public Long getCreatedAt() {
- return createdAt;
- }
-
- UserDto setCreatedAt(long createdAt) {
- this.createdAt = createdAt;
- return this;
- }
-
- public Long getUpdatedAt() {
- return updatedAt;
- }
-
- UserDto setUpdatedAt(long updatedAt) {
- this.updatedAt = updatedAt;
- return this;
- }
-
@CheckForNull
public String getHomepageType() {
return homepageType;
return this;
}
+ @CheckForNull
+ public String getOrganizationUuid() {
+ return organizationUuid;
+ }
+
+ public UserDto setOrganizationUuid(@Nullable String organizationUuid) {
+ this.organizationUuid = organizationUuid;
+ return this;
+ }
+
+ public Long getCreatedAt() {
+ return createdAt;
+ }
+
+ UserDto setCreatedAt(long createdAt) {
+ this.createdAt = createdAt;
+ return this;
+ }
+
+ public Long getUpdatedAt() {
+ return updatedAt;
+ }
+
+ UserDto setUpdatedAt(long updatedAt) {
+ this.updatedAt = updatedAt;
+ return this;
+ }
+
public DefaultUser toUser() {
return new DefaultUser()
.setLogin(login)
org.url as "url",
org.avatar_url as "avatarUrl",
org.guarded as "guarded",
- org.user_id as "userId",
org.created_at as "createdAt",
org.updated_at as "updatedAt"
</sql>
avatar_url,
guarded,
new_project_private,
- user_id,
default_quality_gate_uuid,
created_at,
updated_at
#{organization.avatarUrl, jdbcType=VARCHAR},
#{organization.guarded, jdbcType=BOOLEAN},
#{newProjectPrivate, jdbcType=BOOLEAN},
- #{organization.userId, jdbcType=INTEGER},
#{organization.defaultQualityGateUuid, jdbcType=VARCHAR},
#{organization.createdAt, jdbcType=BIGINT},
#{organization.updatedAt, jdbcType=BIGINT}
<update id="update" parameterType="Organization">
update organizations
set
+ kee = #{organization.key, jdbcType=VARCHAR},
name = #{organization.name, jdbcType=VARCHAR},
description = #{organization.description, jdbcType=VARCHAR},
url = #{organization.url, jdbcType=VARCHAR},
u.user_local as "local",
u.is_root as "root",
u.onboarded as "onboarded",
- u.created_at as "createdAt",
- u.updated_at as "updatedAt",
u.homepage_type as "homepageType",
- u.homepage_parameter as "homepageParameter"
+ u.homepage_parameter as "homepageParameter",
+ u.organization_uuid as organizationUuid,
+ u.created_at as "createdAt",
+ u.updated_at as "updatedAt"
</sql>
<select id="selectByUuid" parameterType="String" resultType="User">
hash_method,
is_root,
onboarded,
- created_at,
- updated_at,
homepage_type,
- homepage_parameter
+ homepage_parameter,
+ organization_uuid,
+ created_at,
+ updated_at
) values (
#{user.uuid,jdbcType=VARCHAR},
#{user.login,jdbcType=VARCHAR},
#{user.hashMethod,jdbcType=VARCHAR},
#{user.root,jdbcType=BOOLEAN},
#{user.onboarded,jdbcType=BOOLEAN},
- #{user.createdAt,jdbcType=BIGINT},
- #{user.updatedAt,jdbcType=BIGINT},
#{user.homepageType,jdbcType=VARCHAR},
- #{user.homepageParameter,jdbcType=VARCHAR}
+ #{user.homepageParameter,jdbcType=VARCHAR},
+ #{user.organizationUuid,jdbcType=VARCHAR},
+ #{user.createdAt,jdbcType=BIGINT},
+ #{user.updatedAt,jdbcType=BIGINT}
)
</insert>
salt = #{user.salt, jdbcType=VARCHAR},
crypted_password = #{user.cryptedPassword, jdbcType=BIGINT},
hash_method = #{user.hashMethod, jdbcType=VARCHAR},
- updated_at = #{user.updatedAt,jdbcType=BIGINT},
homepage_type = #{user.homepageType, jdbcType=VARCHAR},
- homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR}
+ homepage_parameter = #{user.homepageParameter, jdbcType=VARCHAR},
+ organization_uuid = #{user.organizationUuid, jdbcType=VARCHAR},
+ updated_at = #{user.updatedAt,jdbcType=BIGINT}
where
uuid = #{user.uuid, jdbcType=VARCHAR}
</update>
.setUrl("the url 1")
.setAvatarUrl("the avatar url 1")
.setGuarded(false)
- .setDefaultQualityGateUuid("1")
- .setUserId(1_000);
+ .setDefaultQualityGateUuid("1");
private static final OrganizationDto ORGANIZATION_DTO_2 = new OrganizationDto()
.setUuid("uuid 2")
.setKey("the_key 2")
.setUrl("the url 2")
.setAvatarUrl("the avatar url 2")
.setGuarded(true)
- .setDefaultQualityGateUuid("1")
- .setUserId(2_000);
+ .setDefaultQualityGateUuid("1");
private static final String PERMISSION_1 = "foo";
private static final String PERMISSION_2 = "bar";
private System2 system2 = mock(System2.class);
@Rule
- public final DbTester dbTester = DbTester.create(system2).setDisableDefaultOrganization(true);
+ public final DbTester db = DbTester.create(system2).setDisableDefaultOrganization(true);
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private DbClient dbClient = dbTester.getDbClient();
- private DbSession dbSession = dbTester.getSession();
+ private DbClient dbClient = db.getDbClient();
+ private DbSession dbSession = db.getSession();
private OrganizationDao underTest = dbClient.organizationDao();
@Test
public void description_url_avatarUrl_and_userId_are_optional() {
when(system2.now()).thenReturn(SOME_DATE);
- insertOrganization(copyOf(ORGANIZATION_DTO_1).setDescription(null).setUrl(null).setAvatarUrl(null).setUserId(null));
+ insertOrganization(copyOf(ORGANIZATION_DTO_1).setDescription(null).setUrl(null).setAvatarUrl(null));
Map<String, Object> row = selectSingleRow();
assertThat(row.get("uuid")).isEqualTo(ORGANIZATION_DTO_1.getUuid());
}
private Object toBool(boolean guarded) {
- Dialect dialect = dbTester.database().getDialect();
+ Dialect dialect = db.database().getDialect();
if (dialect.getId().equals(Oracle.ID)) {
return guarded ? 1L : 0L;
}
@Test
public void selectByQuery_filter_on_a_member() {
- OrganizationDto organization = dbTester.organizations().insert();
- OrganizationDto anotherOrganization = dbTester.organizations().insert();
- OrganizationDto organizationWithoutMember = dbTester.organizations().insert();
- UserDto user = dbTester.users().insertUser();
- dbTester.organizations().addMember(organization, user);
- dbTester.organizations().addMember(anotherOrganization, user);
+ OrganizationDto organization = db.organizations().insert();
+ OrganizationDto anotherOrganization = db.organizations().insert();
+ OrganizationDto organizationWithoutMember = db.organizations().insert();
+ UserDto user = db.users().insertUser();
+ db.organizations().addMember(organization, user);
+ db.organizations().addMember(anotherOrganization, user);
List<OrganizationDto> result = underTest.selectByQuery(dbSession, OrganizationQuery.newOrganizationQueryBuilder().setMember(user.getId()).build(), forPage(1).andSize(100));
@Test
public void selectByQuery_filter_on_a_member_and_keys() {
- OrganizationDto organization = dbTester.organizations().insert();
- OrganizationDto anotherOrganization = dbTester.organizations().insert();
- OrganizationDto organizationWithoutKeyProvided = dbTester.organizations().insert();
- OrganizationDto organizationWithoutMember = dbTester.organizations().insert();
- UserDto user = dbTester.users().insertUser();
- dbTester.organizations().addMember(organization, user);
- dbTester.organizations().addMember(anotherOrganization, user);
- dbTester.organizations().addMember(organizationWithoutKeyProvided, user);
+ OrganizationDto organization = db.organizations().insert();
+ OrganizationDto anotherOrganization = db.organizations().insert();
+ OrganizationDto organizationWithoutKeyProvided = db.organizations().insert();
+ OrganizationDto organizationWithoutMember = db.organizations().insert();
+ UserDto user = db.users().insertUser();
+ db.organizations().addMember(organization, user);
+ db.organizations().addMember(anotherOrganization, user);
+ db.organizations().addMember(organizationWithoutKeyProvided, user);
List<OrganizationDto> result = underTest.selectByQuery(dbSession, OrganizationQuery.newOrganizationQueryBuilder()
.setKeys(Arrays.asList(organization.getKey(), anotherOrganization.getKey(), organizationWithoutMember.getKey()))
@Test
public void setDefaultQualityGate() {
when(system2.now()).thenReturn(DATE_3);
- OrganizationDto organization = dbTester.organizations().insert();
- QGateWithOrgDto qualityGate = dbTester.qualityGates().insertQualityGate(organization);
+ OrganizationDto organization = db.organizations().insert();
+ QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
underTest.setDefaultQualityGate(dbSession, organization, qualityGate);
- dbTester.commit();
+ db.commit();
assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(qualityGate.getUuid());
verifyOrganizationUpdatedAt(organization.getUuid(), DATE_3);
}
@Test
- public void update_does_not_update_key_nor_createdAt() {
+ public void update_does_not_update_createdAt() {
when(system2.now()).thenReturn(DATE_1);
insertOrganization(ORGANIZATION_DTO_1);
Map<String, Object> row = selectSingleRow();
assertThat(row.get("uuid")).isEqualTo(ORGANIZATION_DTO_1.getUuid());
- assertThat(row.get("key")).isEqualTo(ORGANIZATION_DTO_1.getKey());
+ assertThat(row.get("key")).isEqualTo("new key");
assertThat(row.get("name")).isEqualTo("new name");
assertThat(row.get("description")).isEqualTo("new description");
assertThat(row.get("url")).isEqualTo("new url");
String anotherUuid = "uuid";
insertOrganization(copyOf(ORGANIZATION_DTO_1).setUuid(anotherUuid).setKey("key"));
- assertThat(dbTester.countRowsOfTable("organizations")).isEqualTo(2);
+ assertThat(db.countRowsOfTable("organizations")).isEqualTo(2);
assertThat(underTest.deleteByUuid(dbSession, anotherUuid)).isEqualTo(1);
dbSession.commit();
assertThat(underTest.selectByUuid(dbSession, anotherUuid)).isEmpty();
assertThat(underTest.selectByUuid(dbSession, ORGANIZATION_DTO_1.getUuid())).isNotEmpty();
- assertThat(dbTester.countRowsOfTable("organizations")).isEqualTo(1);
+ assertThat(db.countRowsOfTable("organizations")).isEqualTo(1);
assertThat(underTest.deleteByUuid(dbSession, anotherUuid)).isEqualTo(0);
assertThat(underTest.deleteByUuid(dbSession, ORGANIZATION_DTO_1.getUuid())).isEqualTo(1);
dbSession.commit();
assertThat(underTest.selectByUuid(dbSession, ORGANIZATION_DTO_1.getUuid())).isEmpty();
- assertThat(dbTester.countRowsOfTable("organizations")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("organizations")).isEqualTo(0);
}
@Test
public void selectByPermission_returns_organization_when_user_has_ADMIN_user_permission_on_some_organization() {
- UserDto user = dbTester.users().insertUser();
- OrganizationDto organization1 = dbTester.organizations().insert();
- dbTester.users().insertPermissionOnUser(organization1, user, PERMISSION_2);
- OrganizationDto organization2 = dbTester.organizations().insert();
- dbTester.users().insertPermissionOnUser(organization2, user, PERMISSION_2);
- UserDto otherUser = dbTester.users().insertUser();
- OrganizationDto organization3 = dbTester.organizations().insert();
- dbTester.users().insertPermissionOnUser(organization3, otherUser, PERMISSION_2);
+ UserDto user = db.users().insertUser();
+ OrganizationDto organization1 = db.organizations().insert();
+ db.users().insertPermissionOnUser(organization1, user, PERMISSION_2);
+ OrganizationDto organization2 = db.organizations().insert();
+ db.users().insertPermissionOnUser(organization2, user, PERMISSION_2);
+ UserDto otherUser = db.users().insertUser();
+ OrganizationDto organization3 = db.organizations().insert();
+ db.users().insertPermissionOnUser(organization3, otherUser, PERMISSION_2);
assertThat(underTest.selectByPermission(dbSession, user.getId(), PERMISSION_2))
.extracting(OrganizationDto::getUuid)
@Test
public void selectByPermission_returns_organization_when_user_has_ADMIN_group_permission_on_some_organization() {
- UserDto user = dbTester.users().insertUser();
- OrganizationDto organization1 = dbTester.organizations().insert();
- GroupDto defaultGroup = dbTester.users().insertGroup(organization1);
- dbTester.users().insertPermissionOnGroup(defaultGroup, PERMISSION_1);
- dbTester.users().insertMember(defaultGroup, user);
- OrganizationDto organization2 = dbTester.organizations().insert();
- GroupDto group1 = dbTester.users().insertGroup(organization2);
- dbTester.users().insertPermissionOnGroup(group1, PERMISSION_1);
- dbTester.users().insertMember(group1, user);
- UserDto otherUser = dbTester.users().insertUser();
- OrganizationDto organization3 = dbTester.organizations().insert();
- GroupDto group2 = dbTester.users().insertGroup(organization3);
- dbTester.users().insertPermissionOnGroup(group2, PERMISSION_1);
- dbTester.users().insertMember(group2, otherUser);
+ UserDto user = db.users().insertUser();
+ OrganizationDto organization1 = db.organizations().insert();
+ GroupDto defaultGroup = db.users().insertGroup(organization1);
+ db.users().insertPermissionOnGroup(defaultGroup, PERMISSION_1);
+ db.users().insertMember(defaultGroup, user);
+ OrganizationDto organization2 = db.organizations().insert();
+ GroupDto group1 = db.users().insertGroup(organization2);
+ db.users().insertPermissionOnGroup(group1, PERMISSION_1);
+ db.users().insertMember(group1, user);
+ UserDto otherUser = db.users().insertUser();
+ OrganizationDto organization3 = db.organizations().insert();
+ GroupDto group2 = db.users().insertGroup(organization3);
+ db.users().insertPermissionOnGroup(group2, PERMISSION_1);
+ db.users().insertMember(group2, otherUser);
assertThat(underTest.selectByPermission(dbSession, user.getId(), PERMISSION_1))
.extracting(OrganizationDto::getUuid)
@Test
public void selectByPermission_return_organization_only_once_even_if_user_has_ADMIN_permission_twice_or_more() {
String permission = "destroy";
- UserDto user = dbTester.users().insertUser();
- OrganizationDto organization = dbTester.organizations().insert();
- GroupDto group1 = dbTester.users().insertGroup(organization);
- dbTester.users().insertPermissionOnGroup(group1, permission);
- dbTester.users().insertMember(group1, user);
- GroupDto group2 = dbTester.users().insertGroup(organization);
- dbTester.users().insertPermissionOnGroup(group2, permission);
- dbTester.users().insertMember(group2, user);
- dbTester.users().insertPermissionOnUser(organization, user, permission);
+ UserDto user = db.users().insertUser();
+ OrganizationDto organization = db.organizations().insert();
+ GroupDto group1 = db.users().insertGroup(organization);
+ db.users().insertPermissionOnGroup(group1, permission);
+ db.users().insertMember(group1, user);
+ GroupDto group2 = db.users().insertGroup(organization);
+ db.users().insertPermissionOnGroup(group2, permission);
+ db.users().insertMember(group2, user);
+ db.users().insertPermissionOnUser(organization, user, permission);
assertThat(underTest.selectByPermission(dbSession, user.getId(), permission))
.extracting(OrganizationDto::getUuid)
@Test
public void selectByPermission_returns_organization_only_if_user_has_specific_permission_by_user_permission() {
- OrganizationDto organization = dbTester.organizations().insert();
- OrganizationDto otherOrganization = dbTester.organizations().insert();
- UserDto user = dbTester.users().insertUser();
- dbTester.users().insertPermissionOnUser(organization, user, PERMISSION_1);
- dbTester.users().insertPermissionOnUser(otherOrganization, user, PERMISSION_2);
- UserDto otherUser = dbTester.users().insertUser();
- dbTester.users().insertPermissionOnUser(organization, otherUser, PERMISSION_2);
- dbTester.users().insertPermissionOnUser(otherOrganization, otherUser, PERMISSION_1);
+ OrganizationDto organization = db.organizations().insert();
+ OrganizationDto otherOrganization = db.organizations().insert();
+ UserDto user = db.users().insertUser();
+ db.users().insertPermissionOnUser(organization, user, PERMISSION_1);
+ db.users().insertPermissionOnUser(otherOrganization, user, PERMISSION_2);
+ UserDto otherUser = db.users().insertUser();
+ db.users().insertPermissionOnUser(organization, otherUser, PERMISSION_2);
+ db.users().insertPermissionOnUser(otherOrganization, otherUser, PERMISSION_1);
assertThat(underTest.selectByPermission(dbSession, user.getId(), PERMISSION_1))
.extracting(OrganizationDto::getUuid)
@Test
public void selectByPermission_returns_organization_only_if_user_has_specific_permission_by_group_permission() {
- OrganizationDto organization = dbTester.organizations().insert();
- OrganizationDto otherOrganization = dbTester.organizations().insert();
- GroupDto group1 = dbTester.users().insertGroup(organization);
- GroupDto group2 = dbTester.users().insertGroup(organization);
- GroupDto otherGroup1 = dbTester.users().insertGroup(otherOrganization);
- GroupDto otherGroup2 = dbTester.users().insertGroup(otherOrganization);
- dbTester.users().insertPermissionOnGroup(group1, PERMISSION_1);
- dbTester.users().insertPermissionOnGroup(otherGroup2, PERMISSION_2);
- dbTester.users().insertPermissionOnGroup(group2, PERMISSION_2);
- dbTester.users().insertPermissionOnGroup(otherGroup1, PERMISSION_1);
- UserDto user = dbTester.users().insertUser();
- dbTester.users().insertMember(group1, user);
- dbTester.users().insertMember(otherGroup2, user);
- UserDto otherUser = dbTester.users().insertUser();
- dbTester.users().insertMember(group2, otherUser);
- dbTester.users().insertMember(otherGroup1, otherUser);
+ OrganizationDto organization = db.organizations().insert();
+ OrganizationDto otherOrganization = db.organizations().insert();
+ GroupDto group1 = db.users().insertGroup(organization);
+ GroupDto group2 = db.users().insertGroup(organization);
+ GroupDto otherGroup1 = db.users().insertGroup(otherOrganization);
+ GroupDto otherGroup2 = db.users().insertGroup(otherOrganization);
+ db.users().insertPermissionOnGroup(group1, PERMISSION_1);
+ db.users().insertPermissionOnGroup(otherGroup2, PERMISSION_2);
+ db.users().insertPermissionOnGroup(group2, PERMISSION_2);
+ db.users().insertPermissionOnGroup(otherGroup1, PERMISSION_1);
+ UserDto user = db.users().insertUser();
+ db.users().insertMember(group1, user);
+ db.users().insertMember(otherGroup2, user);
+ UserDto otherUser = db.users().insertUser();
+ db.users().insertMember(group2, otherUser);
+ db.users().insertMember(otherGroup1, otherUser);
assertThat(underTest.selectByPermission(dbSession, user.getId(), PERMISSION_1))
.extracting(OrganizationDto::getUuid)
}
private void dirtyInsertWithDefaultTemplate(String organizationUuid, @Nullable String project, @Nullable String view) {
- try (Connection connection = dbTester.database().getDataSource().getConnection();
+ try (Connection connection = db.database().getDataSource().getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(
"insert into organizations" +
" (" +
assertThat(dto.getUrl()).isEqualTo(ORGANIZATION_DTO_1.getUrl());
assertThat(dto.isGuarded()).isEqualTo(ORGANIZATION_DTO_1.isGuarded());
assertThat(dto.getAvatarUrl()).isEqualTo(ORGANIZATION_DTO_1.getAvatarUrl());
- assertThat(dto.getUserId()).isEqualTo(ORGANIZATION_DTO_1.getUserId());
assertThat(dto.getCreatedAt()).isEqualTo(ORGANIZATION_DTO_1.getCreatedAt());
assertThat(dto.getUpdatedAt()).isEqualTo(ORGANIZATION_DTO_1.getUpdatedAt());
}
assertThat(dto.getDescription()).isEqualTo(expected.getDescription());
assertThat(dto.getUrl()).isEqualTo(expected.getUrl());
assertThat(dto.isGuarded()).isEqualTo(expected.isGuarded());
- assertThat(dto.getUserId()).isEqualTo(expected.getUserId());
assertThat(dto.getAvatarUrl()).isEqualTo(expected.getAvatarUrl());
assertThat(dto.getCreatedAt()).isEqualTo(expected.getCreatedAt());
assertThat(dto.getUpdatedAt()).isEqualTo(expected.getUpdatedAt());
}
private Map<String, Object> selectSingleRow() {
- List<Map<String, Object>> rows = dbTester.select("select" +
+ List<Map<String, Object>> rows = db.select("select" +
" uuid as \"uuid\", kee as \"key\", name as \"name\", description as \"description\", url as \"url\", avatar_url as \"avatarUrl\"," +
- " guarded as \"guarded\", user_id as \"userId\"," +
+ " guarded as \"guarded\"," +
" created_at as \"createdAt\", updated_at as \"updatedAt\"," +
" default_perm_template_project as \"projectDefaultPermTemplate\"," +
" default_perm_template_view as \"viewDefaultPermTemplate\"," +
}
private void verifyOrganizationUpdatedAt(String organization, Long updatedAt) {
- Map<String, Object> row = dbTester.selectFirst(dbTester.getSession(), String.format("select updated_at as \"updatedAt\" from organizations where uuid='%s'", organization));
+ Map<String, Object> row = db.selectFirst(db.getSession(), String.format("select updated_at as \"updatedAt\" from organizations where uuid='%s'", organization));
assertThat(row.get("updatedAt")).isEqualTo(updatedAt);
}
}
.setExternalIdentityProvider("github")
.setExternalId("EXT_ID")
.setLocal(true)
- .setCreatedAt(date)
- .setUpdatedAt(date)
.setHomepageType("project")
- .setHomepageParameter("OB1");
+ .setHomepageParameter("OB1")
+ .setOrganizationUuid("ORG_UUID")
+ .setCreatedAt(date)
+ .setUpdatedAt(date);
underTest.insert(db.getSession(), userDto);
db.getSession().commit();
assertThat(user.isRoot()).isFalse();
assertThat(user.getHomepageType()).isEqualTo("project");
assertThat(user.getHomepageParameter()).isEqualTo("OB1");
+ assertThat(user.getOrganizationUuid()).isEqualTo("ORG_UUID");
}
@Test
.setEmail("jo@hn.com")
.setActive(true)
.setLocal(true)
- .setOnboarded(false));
+ .setOnboarded(false)
+ .setOrganizationUuid("OLD_ORG_UUID"));
underTest.update(db.getSession(), newUserDto()
.setUuid(user.getUuid())
.setExternalId("EXT_ID")
.setLocal(false)
.setHomepageType("project")
- .setHomepageParameter("OB1"));
+ .setHomepageParameter("OB1")
+ .setOrganizationUuid("ORG_UUID"));
UserDto reloaded = underTest.selectByUuid(db.getSession(), user.getUuid());
assertThat(reloaded).isNotNull();
assertThat(reloaded.isRoot()).isFalse();
assertThat(reloaded.getHomepageType()).isEqualTo("project");
assertThat(reloaded.getHomepageParameter()).isEqualTo("OB1");
+ assertThat(reloaded.getOrganizationUuid()).isEqualTo("ORG_UUID");
}
@Test
.setExternalIdentityProvider(randomAlphanumeric(40))
.setSalt(randomAlphanumeric(40))
.setCryptedPassword(randomAlphanumeric(40))
+ // Default quality gate should be set explicitly when needed in tests
+ .setOrganizationUuid("_NOT_SET_")
.setCreatedAt(nextLong())
.setUpdatedAt(nextLong());
}
--- /dev/null
+/*
+ * 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.UUID_SIZE;
+import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddOrganizationUuidToUsers extends DdlChange {
+
+ public AddOrganizationUuidToUsers(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AddColumnsBuilder(getDialect(), "users")
+ .addColumn(newVarcharColumnDefBuilder()
+ .setColumnName("organization_uuid")
+ .setLimit(UUID_SIZE)
+ .setIsNullable(true)
+ .build())
+ .build());
+ }
+
+}
.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)
-
+ .add(2115, "Add ORGANIZATION_UUID on table users", AddOrganizationUuidToUsers.class)
+ .add(2116, "Populate ORGANIZATION_UUID in table users", PopulateOrganizationUuidOnUsers.class)
+ .add(2117, "Drop USER_ID from table organizations", DropUserIdFromOrganizations.class)
;
}
}
--- /dev/null
+/*
+ * 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.DropColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class DropUserIdFromOrganizations extends DdlChange {
+
+ public DropUserIdFromOrganizations(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new DropColumnsBuilder(getDialect(), "organizations", "user_id").build());
+ }
+}
--- /dev/null
+/*
+ * 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 PopulateOrganizationUuidOnUsers extends DataChange {
+
+ private final System2 system2;
+
+ public PopulateOrganizationUuidOnUsers(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 u.id, o.uuid FROM users u " +
+ "INNER JOIN organizations o ON o.user_id=u.id " +
+ "WHERE u.organization_uuid IS NULL");
+ massUpdate.update("UPDATE users SET organization_uuid=?, updated_at=? WHERE id=?");
+
+ long now = system2.now();
+ massUpdate.execute((row, update) -> {
+ long id = row.getLong(1);
+ String organizationUuid = row.getString(2);
+ update.setString(1, organizationUuid);
+ update.setLong(2, now);
+ update.setLong(3, id);
+ return true;
+ });
+ }
+}
}
@Test
- public void migration_is_reentrant() throws SQLException {
+ public void migration_is_not_reentrant() throws SQLException {
underTest.execute();
expectedException.expect(IllegalStateException.class);
--- /dev/null
+/*
+ * 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 AddOrganizationUuidToUsersTest {
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(AddOrganizationUuidToUsersTest.class, "users.sql");
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private AddOrganizationUuidToUsers underTest = new AddOrganizationUuidToUsers(db.database());
+
+ @Test
+ public void column_is_added_to_table() throws SQLException {
+ underTest.execute();
+
+ db.assertColumnDefinition("users", "organization_uuid", VARCHAR, 40, true);
+ }
+
+ @Test
+ public void migration_is_not_reentrant() throws SQLException {
+ underTest.execute();
+
+ expectedException.expect(IllegalStateException.class);
+
+ underTest.execute();
+ }
+
+}
@Test
public void verify_migration_count() {
- verifyMigrationCount(underTest, 15);
+ verifyMigrationCount(underTest, 18);
}
}
--- /dev/null
+/*
+ * 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 DropUserIdFromOrganizationsTest {
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(DropUserIdFromOrganizationsTest.class, "organizations.sql");
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private DropUserIdFromOrganizations underTest = new DropUserIdFromOrganizations(db.database());
+
+ @Test
+ public void column_is_removed_from_table() throws SQLException {
+ underTest.execute();
+
+ db.assertColumnDoesNotExist("organizations", "user_id");
+ }
+
+ @Test
+ public void migration_is_not_reentrant() throws SQLException {
+ underTest.execute();
+
+ expectedException.expect(IllegalStateException.class);
+
+ underTest.execute();
+ }
+
+}
--- /dev/null
+/*
+ * 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 PopulateOrganizationUuidOnUsersTest {
+
+ 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(PopulateOrganizationUuidOnUsersTest.class, "schema.sql");
+
+ private System2 system2 = new TestSystem2().setNow(NOW);
+
+ private PopulateOrganizationUuidOnUsers underTest = new PopulateOrganizationUuidOnUsers(db.database(), system2);
+
+ @Test
+ public void update_users() throws SQLException {
+ // User not migrated
+ long userId = insertUser("USER_1", null);
+ insertOrganization("ORG_1", userId);
+ // User already migrated
+ insertOrganization("ORG_2", null);
+ insertUser("USER_2", "ORG_2");
+
+ underTest.execute();
+
+ assertUsers(
+ tuple("USER_1", "ORG_1", NOW),
+ tuple("USER_2", "ORG_2", PAST));
+ }
+
+ @Test
+ public void does_nothing_when_no_personal_organization() throws SQLException {
+ insertUser("USER_1", null);
+ insertUser("USER_2", null);
+
+ underTest.execute();
+
+ assertUsers(
+ tuple("USER_1", null, PAST),
+ tuple("USER_2", null, PAST));
+ }
+
+ @Test
+ public void migration_is_reentrant() throws SQLException {
+ long userId = insertUser("USER_1", null);
+ insertOrganization("ORG_1", userId);
+ insertOrganization("ORG_2", null);
+ insertUser("USER_2", "ORG_2");
+
+ underTest.execute();
+ underTest.execute();
+
+ assertUsers(
+ tuple("USER_1", "ORG_1", NOW),
+ tuple("USER_2", "ORG_2", PAST));
+ }
+
+ private void assertUsers(Tuple... expectedTuples) {
+ assertThat(db.select("SELECT LOGIN, ORGANIZATION_UUID, UPDATED_AT FROM USERS")
+ .stream()
+ .map(map -> new Tuple(map.get("LOGIN"), map.get("ORGANIZATION_UUID"), map.get("UPDATED_AT")))
+ .collect(Collectors.toList()))
+ .containsExactlyInAnyOrder(expectedTuples);
+ }
+
+ private long insertUser(String uuid, @Nullable String organizationUuid) {
+ db.executeInsert("USERS",
+ "ORGANIZATION_UUID", organizationUuid,
+ "UUID", uuid,
+ "LOGIN", uuid,
+ "EXTERNAL_ID", uuid,
+ "EXTERNAL_LOGIN", uuid,
+ "EXTERNAL_IDENTITY_PROVIDER", uuid,
+ "IS_ROOT", false,
+ "ONBOARDED", false,
+ "CREATED_AT", PAST,
+ "UPDATED_AT", PAST);
+ return (long) db.selectFirst(String.format("SELECT ID FROM USERS WHERE uuid='%s'", uuid)).get("ID");
+ }
+
+ private void insertOrganization(String uuid, @Nullable Long userId) {
+ db.executeInsert("ORGANIZATIONS",
+ "UUID", uuid,
+ "USER_ID", userId,
+ "KEE", uuid,
+ "NAME", uuid,
+ "GUARDED", true,
+ "DEFAULT_QUALITY_GATE_UUID", uuid,
+ "NEW_PROJECT_PRIVATE", false,
+ "CREATED_AT", PAST,
+ "UPDATED_AT", PAST);
+ }
+
+}
\ No newline at end of file
--- /dev/null
+CREATE TABLE "USERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(40) NOT NULL,
+ "LOGIN" VARCHAR(255) NOT NULL,
+ "NAME" VARCHAR(200),
+ "EMAIL" VARCHAR(100),
+ "CRYPTED_PASSWORD" VARCHAR(100),
+ "SALT" VARCHAR(40),
+ "HASH_METHOD" VARCHAR(10),
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" VARCHAR(4000),
+ "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,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "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");
--- /dev/null
+CREATE TABLE "ORGANIZATIONS" (
+ "UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
+ "KEE" VARCHAR(32) NOT NULL,
+ "NAME" VARCHAR(64) NOT NULL,
+ "DESCRIPTION" VARCHAR(256),
+ "URL" VARCHAR(256),
+ "AVATAR_URL" VARCHAR(256),
+ "GUARDED" BOOLEAN NOT NULL,
+ "USER_ID" INTEGER,
+ "DEFAULT_PERM_TEMPLATE_PROJECT" VARCHAR(40),
+ "DEFAULT_PERM_TEMPLATE_VIEW" VARCHAR(40),
+ "DEFAULT_GROUP_ID" INTEGER,
+ "DEFAULT_QUALITY_GATE_UUID" VARCHAR(40) NOT NULL,
+ "NEW_PROJECT_PRIVATE" BOOLEAN NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "PK_ORGANIZATIONS" ON "ORGANIZATIONS" ("UUID");
+CREATE UNIQUE INDEX "ORGANIZATION_KEY" ON "ORGANIZATIONS" ("KEE");
\ No newline at end of file
--- /dev/null
+CREATE TABLE "USERS" (
+ "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+ "UUID" VARCHAR(40) NOT NULL,
+ "LOGIN" VARCHAR(255) NOT NULL,
+ "NAME" VARCHAR(200),
+ "EMAIL" VARCHAR(100),
+ "CRYPTED_PASSWORD" VARCHAR(100),
+ "SALT" VARCHAR(40),
+ "HASH_METHOD" VARCHAR(10),
+ "ACTIVE" BOOLEAN DEFAULT TRUE,
+ "SCM_ACCOUNTS" VARCHAR(4000),
+ "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,
+ "HOMEPAGE_TYPE" VARCHAR(40),
+ "HOMEPAGE_PARAMETER" VARCHAR(40),
+ "ORGANIZATION_UUID" VARCHAR(40),
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT
+);
+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");
+
+CREATE TABLE "ORGANIZATIONS" (
+ "UUID" VARCHAR(40) NOT NULL PRIMARY KEY,
+ "KEE" VARCHAR(32) NOT NULL,
+ "NAME" VARCHAR(64) NOT NULL,
+ "DESCRIPTION" VARCHAR(256),
+ "URL" VARCHAR(256),
+ "AVATAR_URL" VARCHAR(256),
+ "GUARDED" BOOLEAN NOT NULL,
+ "USER_ID" INTEGER,
+ "DEFAULT_PERM_TEMPLATE_PROJECT" VARCHAR(40),
+ "DEFAULT_PERM_TEMPLATE_VIEW" VARCHAR(40),
+ "DEFAULT_GROUP_ID" INTEGER,
+ "DEFAULT_QUALITY_GATE_UUID" VARCHAR(40) NOT NULL,
+ "NEW_PROJECT_PRIVATE" BOOLEAN NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+CREATE UNIQUE INDEX "PK_ORGANIZATIONS" ON "ORGANIZATIONS" ("UUID");
+CREATE UNIQUE INDEX "ORGANIZATION_KEY" ON "ORGANIZATIONS" ("KEE");
\ No newline at end of file
return user;
}
- @SafeVarargs
- public final User generateMemberOfDefaultOrganization(Consumer<CreateRequest>... populators) {
- User user = generate(populators);
- session.wsClient().organizations().addMember(new AddMemberRequest().setOrganization(DEFAULT_ORGANIZATION_KEY).setLogin(user.getLogin()));
- return user;
- }
-
public UsersService service() {
return session.wsClient().users();
}
}
return Optional.empty();
}
+
+ public final String generateLogin() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "login" + id;
+ }
+
+ public final String generateProviderId() {
+ int id = ID_GENERATOR.getAndIncrement();
+ return "providerId" + id;
+ }
}
return new EmailAlreadyExistsPage();
}
+ public UpdateLoginPage asUpdateLoginPage() {
+ return new UpdateLoginPage();
+ }
+
private static SelenideElement logInLink() {
return Selenide.$(By.linkText("Log in"));
}
--- /dev/null
+/*
+ * 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.sonarqube.qa.util.pageobjects;
+
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+
+public class UpdateLoginPage extends Navigation {
+
+ public UpdateLoginPage shouldHaveProviderName(String providerName) {
+ $(".js-provider-name").shouldHave(text(providerName));
+ return this;
+ }
+
+ public UpdateLoginPage shouldHaveNewAccount(String login) {
+ $(".js-new-account").shouldHave(text(login));
+ return this;
+ }
+
+ public UpdateLoginPage shouldHaveOldLogin(String oldLogin) {
+ $(".js-old-login").shouldHave(text(oldLogin));
+ return this;
+ }
+
+ public UpdateLoginPage shouldHaveOldOrganizationKey(String oldOrganizationKey) {
+ $(".js-old-organization-key").shouldHave(text(oldOrganizationKey));
+ return this;
+ }
+
+ public void clickContinue() {
+ $(".js-continue").click();
+ $(".js-continue").shouldNotBe(visible);
+ }
+
+ public void clickCancel() {
+ $(".js-cancel").click();
+ $(".js-cancel").shouldNotBe(visible);
+ }
+
+}
IdentityProviderRepository.class,
BaseContextFactory.class,
OAuth2ContextFactory.class,
- UserIdentityAuthenticator.class,
+ UserIdentityAuthenticatorImpl.class,
OAuthCsrfVerifier.class,
UserSessionInitializer.class,
JwtSerializer.class,
import static java.net.URLEncoder.encode;
import static java.nio.charset.StandardCharsets.UTF_8;
-class AuthenticationRedirection {
+public class AuthenticationRedirection {
private AuthenticationRedirection() {
// Only static methods
}
- static String encodeMessage(String message) {
+ public static String encodeMessage(String message) {
try {
return encode(message, UTF_8.name());
} catch (UnsupportedEncodingException e) {
}
}
- static void redirectTo(HttpServletResponse response, String url) {
+ public static void redirectTo(HttpServletResponse response, String url) {
try {
response.sendRedirect(url);
} catch (IOException e) {
import org.sonar.api.server.authentication.BaseIdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSessionFactory;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.FORBID;
-
public class BaseContextFactory {
private final ThreadLocalUserSession threadLocalUserSession;
@Override
public void authenticate(UserIdentity userIdentity) {
- UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider, Source.external(identityProvider), FORBID);
+ UserDto userDto = userIdentityAuthenticator.authenticate(
+ UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentity)
+ .setProvider(identityProvider)
+ .setSource(Source.external(identityProvider))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
jwtHttpHandler.generateToken(userDto, request, response);
threadLocalUserSession.set(userSessionFactory.create(userDto));
}
+++ /dev/null
-/*
- * 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.authentication;
-
-import org.sonar.api.server.authentication.IdentityProvider;
-import org.sonar.api.server.authentication.UserIdentity;
-import org.sonar.db.user.UserDto;
-
-import static java.lang.String.format;
-import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage;
-
-/**
- * This exception is used to redirect the user to a page explaining him that his email is already used by another account,
- * and where he has the ability to authenticate by "steeling" this email.
- */
-public class EmailAlreadyExistsException extends RuntimeException {
-
- private static final String PATH = "/sessions/email_already_exists?email=%s&login=%s&provider=%s&existingLogin=%s&existingProvider=%s";
-
- private final String email;
- private final UserDto existingUser;
- private final UserIdentity userIdentity;
- private final IdentityProvider provider;
-
- EmailAlreadyExistsException(String email, UserDto existingUser, UserIdentity userIdentity, IdentityProvider provider) {
- this.email = email;
- this.existingUser = existingUser;
- this.userIdentity = userIdentity;
- this.provider = provider;
- }
-
- public String getPath(String contextPath) {
- return contextPath + format(PATH,
- encodeMessage(email),
- encodeMessage(userIdentity.getProviderLogin()),
- encodeMessage(provider.getKey()),
- encodeMessage(existingUser.getExternalLogin()),
- encodeMessage(existingUser.getExternalIdentityProvider()));
- }
-}
import org.sonar.api.server.authentication.UnauthorizedException;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.authentication.exception.RedirectionException;
import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
oAuthOAuth2AuthenticationParameters.delete(request, response);
authenticationEvent.loginFailure(request, e);
handleAuthenticationError(e, response, getContextPath());
- } catch (EmailAlreadyExistsException e) {
+ } catch (RedirectionException e) {
oAuthOAuth2AuthenticationParameters.delete(request, response);
redirectTo(response, e.getPath(getContextPath()));
} catch (Exception e) {
Optional<Boolean> getAllowEmailShift(HttpServletRequest request);
+ Optional<Boolean> getAllowUpdateLogin(HttpServletRequest request);
+
void delete(HttpServletRequest request, HttpServletResponse response);
}
*/
private static final String ALLOW_EMAIL_SHIFT_PARAMETER = "allowEmailShift";
+ /**
+ * This parameter is used to allow the update of login
+ */
+ private static final String ALLOW_LOGIN_UPDATE_PARAMETER = "allowUpdateLogin";
+
private static final Type JSON_MAP_TYPE = new TypeToken<HashMap<String, String>>() {
}.getType();
public void init(HttpServletRequest request, HttpServletResponse response) {
String returnTo = request.getParameter(RETURN_TO_PARAMETER);
String allowEmailShift = request.getParameter(ALLOW_EMAIL_SHIFT_PARAMETER);
+ String allowLoginUpdate = request.getParameter(ALLOW_LOGIN_UPDATE_PARAMETER);
Map<String, String> parameters = new HashMap<>();
if (isNotBlank(returnTo)) {
parameters.put(RETURN_TO_PARAMETER, returnTo);
if (isNotBlank(allowEmailShift)) {
parameters.put(ALLOW_EMAIL_SHIFT_PARAMETER, allowEmailShift);
}
+ if (isNotBlank(allowLoginUpdate)) {
+ parameters.put(ALLOW_LOGIN_UPDATE_PARAMETER, allowLoginUpdate);
+ }
if (parameters.isEmpty()) {
return;
}
return parameter.map(Boolean::parseBoolean);
}
+ @Override
+ public Optional<Boolean> getAllowUpdateLogin(HttpServletRequest request) {
+ Optional<String> parameter = getParameter(request, ALLOW_LOGIN_UPDATE_PARAMETER);
+ return parameter.map(Boolean::parseBoolean);
+ }
+
private static Optional<String> getParameter(HttpServletRequest request, String parameterKey) {
Optional<javax.servlet.http.Cookie> cookie = findCookie(AUTHENTICATION_COOKIE_NAME, request);
if (!cookie.isPresent()) {
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.authentication.exception.RedirectionException;
import static java.lang.String.format;
import static org.sonar.server.authentication.AuthenticationError.handleAuthenticationError;
oauth2Parameters.delete(request, response);
authenticationEvent.loginFailure(request, e);
handleAuthenticationError(e, response, getContextPath());
- } catch (EmailAlreadyExistsException e) {
+ } catch (RedirectionException e) {
oauth2Parameters.delete(request, response);
redirectTo(response, e.getPath(getContextPath()));
} catch (Exception e) {
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSessionFactory;
import static java.lang.String.format;
import static org.sonar.server.authentication.OAuth2CallbackFilter.CALLBACK_PATH;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.ALLOW;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.WARN;
@ServerSide
public class OAuth2ContextFactory {
@Override
public void authenticate(UserIdentity userIdentity) {
Boolean allowEmailShift = oAuthParameters.getAllowEmailShift(request).orElse(false);
- UserDto userDto = userIdentityAuthenticator.authenticate(userIdentity, identityProvider, AuthenticationEvent.Source.oauth2(identityProvider), allowEmailShift ? ALLOW : WARN);
+ Boolean allowUpdateLogin = oAuthParameters.getAllowUpdateLogin(request).orElse(false);
+ UserDto userDto = userIdentityAuthenticator.authenticate(
+ UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentity)
+ .setProvider(identityProvider)
+ .setSource(AuthenticationEvent.Source.oauth2(identityProvider))
+ .setExistingEmailStrategy(allowEmailShift ? ExistingEmailStrategy.ALLOW : ExistingEmailStrategy.WARN)
+ .setUpdateLoginStrategy(allowUpdateLogin ? UpdateLoginStrategy.ALLOW : UpdateLoginStrategy.WARN)
+ .build());
jwtHttpHandler.generateToken(userDto, request, response);
threadLocalUserSession.set(userSessionFactory.create(userDto));
}
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.trimToNull;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.FORBID;
import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
public class RealmAuthenticator implements Startable {
Collection<String> groups = externalGroupsProvider.doGetGroups(context);
userIdentityBuilder.setGroups(new HashSet<>(groups));
}
- return userIdentityAuthenticator.authenticate(userIdentityBuilder.build(), new ExternalIdentityProvider(), realmEventSource(method), FORBID);
+ return userIdentityAuthenticator.authenticate(
+ UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentityBuilder.build())
+ .setProvider(new ExternalIdentityProvider())
+ .setSource(realmEventSource(method))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
}
private String getLogin(String userLogin) {
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.user.UserDto;
import org.sonar.process.ProcessProperties;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.authentication.event.AuthenticationException;
import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_LOGIN_HEADER;
import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_NAME_HEADER;
import static org.sonar.process.ProcessProperties.Property.SONAR_WEB_SSO_REFRESH_INTERVAL_IN_MINUTES;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.FORBID;
import static org.sonar.server.user.ExternalIdentity.SQ_AUTHORITY;
public class SsoAuthenticator implements Startable {
String groupsValue = getHeaderValue(headerValuesByNames, SONAR_WEB_SSO_GROUPS_HEADER.getKey());
userIdentityBuilder.setGroups(groupsValue == null ? Collections.emptySet() : new HashSet<>(COMA_SPLITTER.splitToList(groupsValue)));
}
- return userIdentityAuthenticator.authenticate(userIdentityBuilder.build(), new SsoIdentityProvider(), Source.sso(), FORBID);
+ return userIdentityAuthenticator.authenticate(
+ UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentityBuilder.build())
+ .setProvider(new SsoIdentityProvider())
+ .setSource(Source.sso())
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
}
@CheckForNull
* 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.authentication;
-import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-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;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserGroupDto;
-import org.sonar.server.authentication.event.AuthenticationEvent;
-import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.organization.DefaultOrganization;
-import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationFlags;
-import org.sonar.server.user.ExternalIdentity;
-import org.sonar.server.user.NewUser;
-import org.sonar.server.user.UpdateUser;
-import org.sonar.server.user.UserUpdater;
-import org.sonar.server.usergroups.DefaultGroupFinder;
-
-import static java.lang.String.format;
-import static java.util.Collections.singletonList;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-public class UserIdentityAuthenticator {
-
- /**
- * Strategy to be executed when the email of the user is already used by another user
- */
- enum ExistingEmailStrategy {
- /**
- * Authentication is allowed, the email is moved from other user to current user
- */
- ALLOW,
- /**
- * Authentication process is stopped, the user is redirected to a page explaining that the email is already used
- */
- WARN,
- /**
- * Forbid authentication of the user
- */
- FORBID
- }
-
- private static final Logger LOGGER = Loggers.get(UserIdentityAuthenticator.class);
-
- private final DbClient dbClient;
- private final UserUpdater userUpdater;
- private final DefaultOrganizationProvider defaultOrganizationProvider;
- private final OrganizationFlags organizationFlags;
- private final DefaultGroupFinder defaultGroupFinder;
-
- public UserIdentityAuthenticator(DbClient dbClient, UserUpdater userUpdater, DefaultOrganizationProvider defaultOrganizationProvider, OrganizationFlags organizationFlags,
- DefaultGroupFinder defaultGroupFinder) {
- this.dbClient = dbClient;
- this.userUpdater = userUpdater;
- this.defaultOrganizationProvider = defaultOrganizationProvider;
- this.organizationFlags = organizationFlags;
- this.defaultGroupFinder = defaultGroupFinder;
- }
-
- public UserDto authenticate(UserIdentity userIdentity, IdentityProvider provider, AuthenticationEvent.Source source, ExistingEmailStrategy existingEmailStrategy) {
- try (DbSession dbSession = dbClient.openSession(false)) {
- UserDto userDto = getUser(dbSession, userIdentity, provider);
- if (userDto == null) {
- return registerNewUser(dbSession, null, userIdentity, provider, source, existingEmailStrategy);
- }
- if (!userDto.isActive()) {
- return registerNewUser(dbSession, userDto, userIdentity, provider, source, existingEmailStrategy);
- }
- return registerExistingUser(dbSession, userDto, userIdentity, provider, source, existingEmailStrategy);
- }
- }
-
- @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 = new UpdateUser()
- .setLogin(identity.getLogin())
- .setEmail(identity.getEmail())
- .setName(identity.getName())
- .setExternalIdentity(new ExternalIdentity(provider.getKey(), identity.getProviderLogin(), identity.getProviderId()));
- Optional<UserDto> otherUserToIndex = validateEmail(dbSession, identity, provider, source, existingEmailStrategy);
- userUpdater.updateAndCommit(dbSession, userDto, update, u -> syncGroups(dbSession, identity, u), toArray(otherUserToIndex));
- return userDto;
- }
-
- private UserDto registerNewUser(DbSession dbSession, @Nullable UserDto disabledUser, UserIdentity identity, IdentityProvider provider, AuthenticationEvent.Source source,
- ExistingEmailStrategy existingEmailStrategy) {
- Optional<UserDto> otherUserToIndex = validateEmail(dbSession, identity, provider, source, existingEmailStrategy);
- 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,
- ExistingEmailStrategy existingEmailStrategy) {
- String email = identity.getEmail();
- if (email == null) {
- return Optional.empty();
- }
- UserDto existingUser = dbClient.userDao().selectByEmail(dbSession, email);
- 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) {
- case ALLOW:
- existingUser.setEmail(null);
- dbClient.userDao().update(dbSession, existingUser);
- return Optional.of(existingUser);
- case WARN:
- throw new EmailAlreadyExistsException(email, existingUser, identity, provider);
- case FORBID:
- throw AuthenticationException.newBuilder()
- .setSource(source)
- .setLogin(identity.getLogin())
- .setMessage(format("Email '%s' is already used", email))
- .setPublicMessage(format(
- "You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.",
- email))
- .build();
- default:
- throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy));
- }
- }
-
- private void syncGroups(DbSession dbSession, UserIdentity userIdentity, UserDto userDto) {
- if (!userIdentity.shouldSyncGroups()) {
- return;
- }
- String userLogin = userIdentity.getLogin();
- Set<String> userGroups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userLogin)).get(userLogin));
- Set<String> identityGroups = userIdentity.getGroups();
- LOGGER.debug("List of groups returned by the identity provider '{}'", identityGroups);
-
- Collection<String> groupsToAdd = Sets.difference(identityGroups, userGroups);
- Collection<String> groupsToRemove = Sets.difference(userGroups, identityGroups);
- Collection<String> allGroups = new ArrayList<>(groupsToAdd);
- allGroups.addAll(groupsToRemove);
- DefaultOrganization defaultOrganization = defaultOrganizationProvider.get();
- Map<String, GroupDto> groupsByName = dbClient.groupDao().selectByNames(dbSession, defaultOrganization.getUuid(), allGroups)
- .stream()
- .collect(uniqueIndex(GroupDto::getName));
-
- addGroups(dbSession, userDto, groupsToAdd, groupsByName);
- removeGroups(dbSession, userDto, groupsToRemove, groupsByName);
- }
-
- private void addGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToAdd, Map<String, GroupDto> groupsByName) {
- groupsToAdd.stream().map(groupsByName::get).filter(Objects::nonNull).forEach(
- groupDto -> {
- LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin());
- dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId()));
- });
- }
-
- private void removeGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToRemove, Map<String, GroupDto> groupsByName) {
- Optional<GroupDto> defaultGroup = getDefaultGroup(dbSession);
- groupsToRemove.stream().map(groupsByName::get)
- .filter(Objects::nonNull)
- // user should be member of default group only when organizations are disabled, as the IdentityProvider API doesn't handle yet
- // organizations
- .filter(group -> !defaultGroup.isPresent() || !group.getId().equals(defaultGroup.get().getId()))
- .forEach(groupDto -> {
- LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin());
- dbClient.userGroupDao().delete(dbSession, groupDto.getId(), userDto.getId());
- });
- }
-
- private Optional<GroupDto> getDefaultGroup(DbSession dbSession) {
- 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();
- }
+public interface UserIdentityAuthenticator {
- private static UserDto[] toArray(Optional<UserDto> userDto) {
- return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {});
- }
+ UserDto authenticate(UserIdentityAuthenticatorParameters authenticatorParameters);
}
--- /dev/null
+/*
+ * 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.authentication;
+
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+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;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserGroupDto;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
+import org.sonar.server.authentication.exception.UpdateLoginRedirectionException;
+import org.sonar.server.organization.DefaultOrganization;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.OrganizationFlags;
+import org.sonar.server.organization.OrganizationUpdater;
+import org.sonar.server.user.ExternalIdentity;
+import org.sonar.server.user.NewUser;
+import org.sonar.server.user.UpdateUser;
+import org.sonar.server.user.UserUpdater;
+import org.sonar.server.usergroups.DefaultGroupFinder;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
+
+public class UserIdentityAuthenticatorImpl implements UserIdentityAuthenticator {
+
+ private static final Logger LOGGER = Loggers.get(UserIdentityAuthenticatorImpl.class);
+
+ private final DbClient dbClient;
+ private final UserUpdater userUpdater;
+ private final DefaultOrganizationProvider defaultOrganizationProvider;
+ private final OrganizationFlags organizationFlags;
+ private final OrganizationUpdater organizationUpdater;
+ private final DefaultGroupFinder defaultGroupFinder;
+
+ public UserIdentityAuthenticatorImpl(DbClient dbClient, UserUpdater userUpdater, DefaultOrganizationProvider defaultOrganizationProvider, OrganizationFlags organizationFlags,
+ OrganizationUpdater organizationUpdater, DefaultGroupFinder defaultGroupFinder) {
+ this.dbClient = dbClient;
+ this.userUpdater = userUpdater;
+ this.defaultOrganizationProvider = defaultOrganizationProvider;
+ this.organizationFlags = organizationFlags;
+ this.organizationUpdater = organizationUpdater;
+ this.defaultGroupFinder = defaultGroupFinder;
+ }
+
+ @Override
+ public UserDto authenticate(UserIdentityAuthenticatorParameters authenticatorParameters) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ UserDto userDto = getUser(dbSession, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider());
+ if (userDto == null) {
+ return registerNewUser(dbSession, null, authenticatorParameters);
+ }
+ if (!userDto.isActive()) {
+ return registerNewUser(dbSession, userDto, authenticatorParameters);
+ }
+ return registerExistingUser(dbSession, userDto, authenticatorParameters);
+ }
+ }
+
+ @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, UserIdentityAuthenticatorParameters authenticatorParameters) {
+ UpdateUser update = new UpdateUser()
+ .setLogin(authenticatorParameters.getUserIdentity().getLogin())
+ .setEmail(authenticatorParameters.getUserIdentity().getEmail())
+ .setName(authenticatorParameters.getUserIdentity().getName())
+ .setExternalIdentity(new ExternalIdentity(
+ authenticatorParameters.getProvider().getKey(),
+ authenticatorParameters.getUserIdentity().getProviderLogin(),
+ authenticatorParameters.getUserIdentity().getProviderId()));
+ detectLoginUpdate(dbSession, userDto, update, authenticatorParameters);
+ Optional<UserDto> otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters);
+ userUpdater.updateAndCommit(dbSession, userDto, update, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex));
+ return userDto;
+ }
+
+ private UserDto registerNewUser(DbSession dbSession, @Nullable UserDto disabledUser, UserIdentityAuthenticatorParameters authenticatorParameters) {
+ Optional<UserDto> otherUserToIndex = detectEmailUpdate(dbSession, authenticatorParameters);
+ NewUser newUser = createNewUser(authenticatorParameters);
+ if (disabledUser == null) {
+ return userUpdater.createAndCommit(dbSession, newUser, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex));
+ }
+ return userUpdater.reactivateAndCommit(dbSession, disabledUser, newUser, u -> syncGroups(dbSession, authenticatorParameters.getUserIdentity(), u), toArray(otherUserToIndex));
+ }
+
+ private Optional<UserDto> detectEmailUpdate(DbSession dbSession, UserIdentityAuthenticatorParameters authenticatorParameters) {
+ String email = authenticatorParameters.getUserIdentity().getEmail();
+ if (email == null) {
+ return Optional.empty();
+ }
+ UserDto existingUser = dbClient.userDao().selectByEmail(dbSession, email);
+ if (existingUser == null
+ || Objects.equals(existingUser.getLogin(), authenticatorParameters.getUserIdentity().getLogin())
+ || (Objects.equals(existingUser.getExternalId(), authenticatorParameters.getUserIdentity().getProviderId())
+ && Objects.equals(existingUser.getExternalIdentityProvider(), authenticatorParameters.getProvider().getKey()))) {
+ return Optional.empty();
+ }
+ ExistingEmailStrategy existingEmailStrategy = authenticatorParameters.getExistingEmailStrategy();
+ switch (existingEmailStrategy) {
+ case ALLOW:
+ existingUser.setEmail(null);
+ dbClient.userDao().update(dbSession, existingUser);
+ return Optional.of(existingUser);
+ case WARN:
+ throw new EmailAlreadyExistsRedirectionException(email, existingUser, authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider());
+ case FORBID:
+ throw AuthenticationException.newBuilder()
+ .setSource(authenticatorParameters.getSource())
+ .setLogin(authenticatorParameters.getUserIdentity().getLogin())
+ .setMessage(format("Email '%s' is already used", email))
+ .setPublicMessage(format(
+ "You can't sign up because email '%s' is already used by an existing user. This means that you probably already registered with another account.",
+ email))
+ .build();
+ default:
+ throw new IllegalStateException(format("Unknown strategy %s", existingEmailStrategy));
+ }
+ }
+
+ private void detectLoginUpdate(DbSession dbSession, UserDto user, UpdateUser update, UserIdentityAuthenticatorParameters authenticatorParameters) {
+ String newLogin = update.login();
+ if (!update.isLoginChanged() || user.getLogin().equals(newLogin)) {
+ return;
+ }
+ if (!organizationFlags.isEnabled(dbSession)) {
+ return;
+ }
+ String personalOrganizationUuid = user.getOrganizationUuid();
+ if (personalOrganizationUuid == null) {
+ return;
+ }
+ Optional<OrganizationDto> personalOrganization = dbClient.organizationDao().selectByUuid(dbSession, personalOrganizationUuid);
+ checkState(personalOrganization.isPresent(),
+ "Cannot find personal organization uuid '%s' for user '%s'", personalOrganizationUuid, user.getLogin());
+ UpdateLoginStrategy updateLoginStrategy = authenticatorParameters.getUpdateLoginStrategy();
+ switch (updateLoginStrategy) {
+ case ALLOW:
+ organizationUpdater.updateOrganizationKey(dbSession, personalOrganization.get(), requireNonNull(newLogin, "new login cannot be null"));
+ return;
+ case WARN:
+ throw new UpdateLoginRedirectionException(authenticatorParameters.getUserIdentity(), authenticatorParameters.getProvider(), user, personalOrganization.get());
+ default:
+ throw new IllegalStateException(format("Unknown strategy %s", updateLoginStrategy));
+ }
+ }
+
+ private void syncGroups(DbSession dbSession, UserIdentity userIdentity, UserDto userDto) {
+ if (!userIdentity.shouldSyncGroups()) {
+ return;
+ }
+ String userLogin = userIdentity.getLogin();
+ Set<String> userGroups = new HashSet<>(dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, singletonList(userLogin)).get(userLogin));
+ Set<String> identityGroups = userIdentity.getGroups();
+ LOGGER.debug("List of groups returned by the identity provider '{}'", identityGroups);
+
+ Collection<String> groupsToAdd = Sets.difference(identityGroups, userGroups);
+ Collection<String> groupsToRemove = Sets.difference(userGroups, identityGroups);
+ Collection<String> allGroups = new ArrayList<>(groupsToAdd);
+ allGroups.addAll(groupsToRemove);
+ DefaultOrganization defaultOrganization = defaultOrganizationProvider.get();
+ Map<String, GroupDto> groupsByName = dbClient.groupDao().selectByNames(dbSession, defaultOrganization.getUuid(), allGroups)
+ .stream()
+ .collect(uniqueIndex(GroupDto::getName));
+
+ addGroups(dbSession, userDto, groupsToAdd, groupsByName);
+ removeGroups(dbSession, userDto, groupsToRemove, groupsByName);
+ }
+
+ private void addGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToAdd, Map<String, GroupDto> groupsByName) {
+ groupsToAdd.stream().map(groupsByName::get).filter(Objects::nonNull).forEach(
+ groupDto -> {
+ LOGGER.debug("Adding group '{}' to user '{}'", groupDto.getName(), userDto.getLogin());
+ dbClient.userGroupDao().insert(dbSession, new UserGroupDto().setGroupId(groupDto.getId()).setUserId(userDto.getId()));
+ });
+ }
+
+ private void removeGroups(DbSession dbSession, UserDto userDto, Collection<String> groupsToRemove, Map<String, GroupDto> groupsByName) {
+ Optional<GroupDto> defaultGroup = getDefaultGroup(dbSession);
+ groupsToRemove.stream().map(groupsByName::get)
+ .filter(Objects::nonNull)
+ // user should be member of default group only when organizations are disabled, as the IdentityProvider API doesn't handle yet
+ // organizations
+ .filter(group -> !defaultGroup.isPresent() || !group.getId().equals(defaultGroup.get().getId()))
+ .forEach(groupDto -> {
+ LOGGER.debug("Removing group '{}' from user '{}'", groupDto.getName(), userDto.getLogin());
+ dbClient.userGroupDao().delete(dbSession, groupDto.getId(), userDto.getId());
+ });
+ }
+
+ private Optional<GroupDto> getDefaultGroup(DbSession dbSession) {
+ return organizationFlags.isEnabled(dbSession) ? Optional.empty() : Optional.of(defaultGroupFinder.findDefaultGroup(dbSession, defaultOrganizationProvider.get().getUuid()));
+ }
+
+ private static NewUser createNewUser(UserIdentityAuthenticatorParameters authenticatorParameters) {
+ String identityProviderKey = authenticatorParameters.getProvider().getKey();
+ if (!authenticatorParameters.getProvider().allowsUsersToSignUp()) {
+ throw AuthenticationException.newBuilder()
+ .setSource(authenticatorParameters.getSource())
+ .setLogin(authenticatorParameters.getUserIdentity().getLogin())
+ .setMessage(format("User signup disabled for provider '%s'", identityProviderKey))
+ .setPublicMessage(format("'%s' users are not allowed to sign up", identityProviderKey))
+ .build();
+ }
+ return NewUser.builder()
+ .setLogin(authenticatorParameters.getUserIdentity().getLogin())
+ .setEmail(authenticatorParameters.getUserIdentity().getEmail())
+ .setName(authenticatorParameters.getUserIdentity().getName())
+ .setExternalIdentity(
+ new ExternalIdentity(
+ identityProviderKey,
+ authenticatorParameters.getUserIdentity().getProviderLogin(),
+ authenticatorParameters.getUserIdentity().getProviderId()))
+ .build();
+ }
+
+ private static UserDto[] toArray(Optional<UserDto> userDto) {
+ return userDto.map(u -> new UserDto[] {u}).orElse(new UserDto[] {});
+ }
+
+}
--- /dev/null
+/*
+ * 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.authentication;
+
+import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+
+import static java.util.Objects.requireNonNull;
+
+class UserIdentityAuthenticatorParameters {
+
+ /**
+ * Strategy to be executed when the email of the user is already used by another user
+ */
+ enum ExistingEmailStrategy {
+ /**
+ * Authentication is allowed, the email is moved from other user to current user
+ */
+ ALLOW,
+ /**
+ * Authentication process is stopped, the user is redirected to a page explaining that the email is already used
+ */
+ WARN,
+ /**
+ * Forbid authentication of the user
+ */
+ FORBID
+ }
+
+ /**
+ * Strategy to be executed when the login of the user is updated
+ */
+ enum UpdateLoginStrategy {
+ /**
+ * Authentication is allowed, the login of the user updated
+ */
+ ALLOW,
+ /**
+ * Authentication process is stopped, the user is redirected to a page explaining that the login will be updated.
+ * It only happens when personal organizations are activated
+ */
+ WARN
+ }
+
+ private final UserIdentity userIdentity;
+ private final IdentityProvider provider;
+ private final AuthenticationEvent.Source source;
+ private final ExistingEmailStrategy existingEmailStrategy;
+ private final UpdateLoginStrategy updateLoginStrategy;
+
+ UserIdentityAuthenticatorParameters(Builder builder) {
+ this.userIdentity = builder.userIdentity;
+ this.provider = builder.provider;
+ this.source = builder.source;
+ this.existingEmailStrategy = builder.existingEmailStrategy;
+ this.updateLoginStrategy = builder.updateLoginStrategy;
+ }
+
+ public UserIdentity getUserIdentity() {
+ return userIdentity;
+ }
+
+ public IdentityProvider getProvider() {
+ return provider;
+ }
+
+ public AuthenticationEvent.Source getSource() {
+ return source;
+ }
+
+ public ExistingEmailStrategy getExistingEmailStrategy() {
+ return existingEmailStrategy;
+ }
+
+ public UpdateLoginStrategy getUpdateLoginStrategy() {
+ return updateLoginStrategy;
+ }
+
+ static UserIdentityAuthenticatorParameters.Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private UserIdentity userIdentity;
+ private IdentityProvider provider;
+ private AuthenticationEvent.Source source;
+ private ExistingEmailStrategy existingEmailStrategy;
+ private UpdateLoginStrategy updateLoginStrategy;
+
+ public Builder setUserIdentity(UserIdentity userIdentity) {
+ this.userIdentity = userIdentity;
+ return this;
+ }
+
+ public Builder setProvider(IdentityProvider provider) {
+ this.provider = provider;
+ return this;
+ }
+
+ public Builder setSource(AuthenticationEvent.Source source) {
+ this.source = source;
+ return this;
+ }
+
+ /**
+ * Strategy to be executed when the email of the user is already used by another user
+ */
+ public Builder setExistingEmailStrategy(ExistingEmailStrategy existingEmailStrategy) {
+ this.existingEmailStrategy = existingEmailStrategy;
+ return this;
+ }
+
+ /**
+ * Strategy to be executed when the login of the user has changed
+ */
+ public Builder setUpdateLoginStrategy(UpdateLoginStrategy updateLoginStrategy) {
+ this.updateLoginStrategy = updateLoginStrategy;
+ return this;
+ }
+
+ public UserIdentityAuthenticatorParameters build() {
+ requireNonNull(userIdentity, "userIdentity must be set");
+ requireNonNull(provider, "identityProvider must be set");
+ requireNonNull(source, "Source must be set");
+ requireNonNull(existingEmailStrategy, "existingEmailStrategy must be set ");
+ requireNonNull(updateLoginStrategy, "updateLoginStrategy must be set");
+ return new UserIdentityAuthenticatorParameters(this);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.authentication.exception;
+
+import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.db.user.UserDto;
+
+import static java.lang.String.format;
+import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage;
+
+/**
+ * This exception is used to redirect the user to a page explaining him that his email is already used by another account,
+ * and where he has the ability to authenticate by "steeling" this email.
+ */
+public class EmailAlreadyExistsRedirectionException extends RedirectionException {
+
+ private static final String PATH = "/sessions/email_already_exists?email=%s&login=%s&provider=%s&existingLogin=%s&existingProvider=%s";
+
+ private final String email;
+ private final UserDto existingUser;
+ private final UserIdentity userIdentity;
+ private final IdentityProvider provider;
+
+ public EmailAlreadyExistsRedirectionException(String email, UserDto existingUser, UserIdentity userIdentity, IdentityProvider provider) {
+ this.email = email;
+ this.existingUser = existingUser;
+ this.userIdentity = userIdentity;
+ this.provider = provider;
+ }
+
+ @Override
+ public String getPath(String contextPath) {
+ return contextPath + format(PATH,
+ encodeMessage(email),
+ encodeMessage(userIdentity.getProviderLogin()),
+ encodeMessage(provider.getKey()),
+ encodeMessage(existingUser.getExternalLogin()),
+ encodeMessage(existingUser.getExternalIdentityProvider()));
+ }
+}
--- /dev/null
+/*
+ * 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.authentication.exception;
+
+public abstract class RedirectionException extends RuntimeException {
+
+ public abstract String getPath(String contextPath);
+}
--- /dev/null
+/*
+ * 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.authentication.exception;
+
+import org.sonar.api.server.authentication.IdentityProvider;
+import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
+
+import static java.lang.String.format;
+import static org.sonar.server.authentication.AuthenticationRedirection.encodeMessage;
+
+/**
+ * This exception is used to redirect the user to a page explaining him that his login will be updated.
+ */
+public class UpdateLoginRedirectionException extends RedirectionException {
+
+ private static final String PATH = "/sessions/update_login?login=%s&providerKey=%s&providerName=%s&oldLogin=%s&oldOrganizationKey=%s";
+
+ private final UserIdentity userIdentity;
+ private final IdentityProvider provider;
+ private final UserDto user;
+ private final OrganizationDto organization;
+
+ public UpdateLoginRedirectionException(UserIdentity userIdentity, IdentityProvider provider, UserDto user, OrganizationDto organization) {
+ this.userIdentity = userIdentity;
+ this.provider = provider;
+ this.user = user;
+ this.organization = organization;
+ }
+
+ @Override
+ public String getPath(String contextPath) {
+ return contextPath + format(PATH,
+ encodeMessage(userIdentity.getProviderLogin()),
+ encodeMessage(provider.getKey()),
+ encodeMessage(provider.getName()),
+ encodeMessage(user.getLogin()),
+ encodeMessage(organization.getKey()));
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.authentication.exception;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+++ /dev/null
-/*
- * 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.organization;
-
-import java.util.Optional;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.db.DbSession;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.usergroups.DefaultGroupCreatorImpl;
-
-import static java.util.Objects.requireNonNull;
-
-public interface OrganizationCreation {
- String OWNERS_GROUP_NAME = "Owners";
- String OWNERS_GROUP_DESCRIPTION_PATTERN = "Owners of organization %s";
- String PERM_TEMPLATE_NAME = "Default template";
- String PERM_TEMPLATE_DESCRIPTION_PATTERN = "Default permission template of organization %s";
- String PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN = "%s's personal organization";
-
- /**
- * Create a new non guarded organization with the specified properties and of which the specified user will assign
- * Administer Organization permission.
- * <p>
- * This method does several operations at once:
- * <ol>
- * <li>create an ungarded organization with the specified details</li>
- * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with all organization wide permissions</li>
- * <li>create a group called {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} with browse permissions</li>
- * <li>make the specified user a member of these groups</li>
- * <li>create a default template for the organization
- * <ul>
- * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li>
- * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li>
- * </ul>
- * </li>
- * <li>this permission template defines the specified permissions (which effectively makes projects public):
- * <ul>
- * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ADMIN ADMIN}</li>
- * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li>
- * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li>
- * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#USER USER}</li>
- * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
- * </ul>
- * </li>
- * </ol>
- * </p>
- *
- * @return the created organization
- *
- * @throws KeyConflictException if an organization with the specified key already exists
- * @throws IllegalArgumentException if any field of {@code newOrganization} is invalid according to {@link OrganizationValidation}
- */
- OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization) throws KeyConflictException;
-
- /**
- * Create a new guarded organization which details are based on the login of the specified User.
- * <p>
- * This method does several operations at once:
- * <ol>
- * <li>
- * create a guarded organization with the details computed from user's details:
- * <ul>
- * <li>key: generated from the user's login</li>
- * <li>name: the user's name if set, otherwise the user's login</li>
- * <li>description: {@link #PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN "[name]'s personal organization"} where name
- * is user name (when non null and non empty) or login</li>
- * <li>url and avatar: null</li>
- * </ul>
- * </li>
- * <li>create a group called {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} with browse permissions</li>
- * <li>make the specified user a member of this group</li>
- * <li>give all organization wide permissions to the user</li>
- * <li>create a default template for the organization
- * <ul>
- * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li>
- * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li>
- * </ul>
- * </li>
- * <li>this permission template defines the specified permissions (which effectively makes projects public and
- * automatically adds new projects to the user's favorites):
- * <ul>
- * <li>project creator : {@link UserRole#ADMIN ADMIN}</li>
- * <li>project creator : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li>
- * <li>project creator : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li>
- * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#USER USER}</li>
- * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
- * </ul>
- * </li>
- * </ol>
- * </p>
- *
- * @return the created organization or empty if feature is disabled
- *
- * @throws IllegalArgumentException if any field of {@code newOrganization} is invalid according to {@link OrganizationValidation}
- * @throws IllegalStateException if an organization with the key generated from the login already exists
- */
- Optional<OrganizationDto> createForUser(DbSession dbSession, UserDto newUser);
-
- final class KeyConflictException extends Exception {
- KeyConflictException(String message) {
- super(message);
- }
- }
-
- final class NewOrganization {
- private final String key;
- private final String name;
- @CheckForNull
- private final String description;
- @CheckForNull
- private final String url;
- @CheckForNull
- private final String avatar;
-
- private NewOrganization(Builder builder) {
- this.key = builder.key;
- this.name = builder.name;
- this.description = builder.description;
- this.url = builder.url;
- this.avatar = builder.avatarUrl;
- }
-
- public String getKey() {
- return key;
- }
-
- public String getName() {
- return name;
- }
-
- @CheckForNull
- public String getDescription() {
- return description;
- }
-
- @CheckForNull
- public String getUrl() {
- return url;
- }
-
- @CheckForNull
- public String getAvatar() {
- return avatar;
- }
-
- public static NewOrganization.Builder newOrganizationBuilder() {
- return new Builder();
- }
-
- public static final class Builder {
- private String key;
- private String name;
- private String description;
- private String url;
- private String avatarUrl;
-
- private Builder() {
- // use factory method
- }
-
- public Builder setKey(String key) {
- this.key = requireNonNull(key, "key can't be null");
- return this;
- }
-
- public Builder setName(String name) {
- this.name = requireNonNull(name, "name can't be null");
- return this;
- }
-
- public Builder setDescription(@Nullable String description) {
- this.description = description;
- return this;
- }
-
- public Builder setUrl(@Nullable String url) {
- this.url = url;
- return this;
- }
-
- public Builder setAvatarUrl(@Nullable String avatarUrl) {
- this.avatarUrl = avatarUrl;
- return this;
- }
-
- public NewOrganization build() {
- requireNonNull(key, "key can't be null");
- requireNonNull(name, "name can't be null");
- return new NewOrganization(this);
- }
- }
- }
-
-}
+++ /dev/null
-/*
- * 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.organization;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.function.Consumer;
-import javax.annotation.Nullable;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.System2;
-import org.sonar.core.config.CorePropertyDefinitions;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.organization.DefaultTemplates;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.organization.OrganizationMemberDto;
-import org.sonar.db.permission.GroupPermissionDto;
-import org.sonar.db.permission.OrganizationPermission;
-import org.sonar.db.permission.UserPermissionDto;
-import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto;
-import org.sonar.db.permission.template.PermissionTemplateDto;
-import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.db.qualityprofile.DefaultQProfileDto;
-import org.sonar.db.qualityprofile.OrgQProfileDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserGroupDto;
-import org.sonar.server.qualityprofile.BuiltInQProfile;
-import org.sonar.server.qualityprofile.BuiltInQProfileRepository;
-import org.sonar.server.qualityprofile.QProfileName;
-import org.sonar.server.user.index.UserIndexer;
-import org.sonar.server.usergroups.DefaultGroupCreator;
-
-import static com.google.common.base.Preconditions.checkState;
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static org.sonar.api.web.UserRole.ADMIN;
-import static org.sonar.api.web.UserRole.CODEVIEWER;
-import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
-import static org.sonar.api.web.UserRole.USER;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-import static org.sonar.db.permission.OrganizationPermission.SCAN;
-import static org.sonar.server.organization.OrganizationCreation.NewOrganization.newOrganizationBuilder;
-
-public class OrganizationCreationImpl implements OrganizationCreation {
-
- private final DbClient dbClient;
- private final System2 system2;
- private final UuidFactory uuidFactory;
- private final OrganizationValidation organizationValidation;
- private final Configuration config;
- private final BuiltInQProfileRepository builtInQProfileRepository;
- private final DefaultGroupCreator defaultGroupCreator;
- private final UserIndexer userIndexer;
-
- public OrganizationCreationImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory,
- OrganizationValidation organizationValidation, Configuration config, UserIndexer userIndexer,
- BuiltInQProfileRepository builtInQProfileRepository, DefaultGroupCreator defaultGroupCreator) {
- this.dbClient = dbClient;
- this.system2 = system2;
- this.uuidFactory = uuidFactory;
- this.organizationValidation = organizationValidation;
- this.config = config;
- this.userIndexer = userIndexer;
- this.builtInQProfileRepository = builtInQProfileRepository;
- this.defaultGroupCreator = defaultGroupCreator;
- }
-
- @Override
- public OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization) throws KeyConflictException {
- validate(newOrganization);
- String key = newOrganization.getKey();
- if (organizationKeyIsUsed(dbSession, key)) {
- throw new KeyConflictException(format("Organization key '%s' is already used", key));
- }
-
- QualityGateDto builtInQualityGate = dbClient.qualityGateDao().selectBuiltIn(dbSession);
- OrganizationDto organization = insertOrganization(dbSession, newOrganization, builtInQualityGate);
- insertOrganizationMember(dbSession, organization, userCreator.getId());
- dbClient.qualityGateDao().associate(dbSession, uuidFactory.create(), organization, builtInQualityGate);
- GroupDto ownerGroup = insertOwnersGroup(dbSession, organization);
- GroupDto defaultGroup = defaultGroupCreator.create(dbSession, organization.getUuid());
- insertDefaultTemplateOnGroups(dbSession, organization, ownerGroup, defaultGroup);
- try (DbSession batchDbSession = dbClient.openSession(true)) {
- insertQualityProfiles(dbSession, batchDbSession, organization);
- addCurrentUserToGroup(dbSession, ownerGroup, userCreator.getId());
- addCurrentUserToGroup(dbSession, defaultGroup, userCreator.getId());
-
- batchDbSession.commit();
-
- // Elasticsearch is updated when DB session is committed
- userIndexer.commitAndIndex(dbSession, userCreator);
-
- return organization;
- }
- }
-
- @Override
- public Optional<OrganizationDto> createForUser(DbSession dbSession, UserDto newUser) {
- if (!isCreatePersonalOrgEnabled()) {
- return Optional.empty();
- }
-
- String nameOrLogin = nameOrLogin(newUser);
- NewOrganization newOrganization = newOrganizationBuilder()
- .setKey(organizationValidation.generateKeyFrom(newUser.getLogin()))
- .setName(toName(nameOrLogin))
- .setDescription(format(PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN, nameOrLogin))
- .build();
- checkState(!organizationKeyIsUsed(dbSession, newOrganization.getKey()),
- "Can't create organization with key '%s' for new user '%s' because an organization with this key already exists",
- newOrganization.getKey(),
- newUser.getLogin());
-
- QualityGateDto builtInQualityGate = dbClient.qualityGateDao().selectBuiltIn(dbSession);
- OrganizationDto organization = insertOrganization(dbSession, newOrganization, builtInQualityGate,
- dto -> dto.setGuarded(true).setUserId(newUser.getId()));
- insertOrganizationMember(dbSession, organization, newUser.getId());
- GroupDto defaultGroup = defaultGroupCreator.create(dbSession, organization.getUuid());
- dbClient.qualityGateDao().associate(dbSession, uuidFactory.create(), organization, builtInQualityGate);
- OrganizationPermission.all()
- .forEach(p -> insertUserPermissions(dbSession, newUser, organization, p));
- insertPersonalOrgDefaultTemplate(dbSession, organization, defaultGroup);
- try (DbSession batchDbSession = dbClient.openSession(true)) {
- insertQualityProfiles(dbSession, batchDbSession, organization);
- addCurrentUserToGroup(dbSession, defaultGroup, newUser.getId());
-
- batchDbSession.commit();
-
- // Elasticsearch is updated when DB session is committed
- userIndexer.commitAndIndex(dbSession, newUser);
-
- return Optional.of(organization);
- }
- }
-
- private static String nameOrLogin(UserDto newUser) {
- String name = newUser.getName();
- if (name == null || name.isEmpty()) {
- return newUser.getLogin();
- }
- return name;
- }
-
- private String toName(String login) {
- String name = login.substring(0, Math.min(login.length(), OrganizationValidation.NAME_MAX_LENGTH));
- // should not happen has login can't be less than 2 chars, but we call it for safety
- organizationValidation.checkName(name);
- return name;
- }
-
- private boolean isCreatePersonalOrgEnabled() {
- return config.getBoolean(CorePropertyDefinitions.ORGANIZATIONS_CREATE_PERSONAL_ORG).orElse(false);
- }
-
- private void validate(NewOrganization newOrganization) {
- requireNonNull(newOrganization, "newOrganization can't be null");
- organizationValidation.checkName(newOrganization.getName());
- organizationValidation.checkKey(newOrganization.getKey());
- organizationValidation.checkDescription(newOrganization.getDescription());
- organizationValidation.checkUrl(newOrganization.getUrl());
- organizationValidation.checkAvatar(newOrganization.getAvatar());
- }
-
- private OrganizationDto insertOrganization(DbSession dbSession, NewOrganization newOrganization, QualityGateDto builtInQualityGate, Consumer<OrganizationDto>... extendCreation) {
- OrganizationDto res = new OrganizationDto()
- .setUuid(uuidFactory.create())
- .setName(newOrganization.getName())
- .setKey(newOrganization.getKey())
- .setDescription(newOrganization.getDescription())
- .setUrl(newOrganization.getUrl())
- .setDefaultQualityGateUuid(builtInQualityGate.getUuid())
- .setAvatarUrl(newOrganization.getAvatar());
- Arrays.stream(extendCreation).forEach(c -> c.accept(res));
- dbClient.organizationDao().insert(dbSession, res, false);
- return res;
- }
-
- private boolean organizationKeyIsUsed(DbSession dbSession, String key) {
- return dbClient.organizationDao().selectByKey(dbSession, key).isPresent();
- }
-
- private void insertDefaultTemplateOnGroups(DbSession dbSession, OrganizationDto organizationDto, GroupDto ownerGroup, GroupDto defaultGroup) {
- Date now = new Date(system2.now());
- PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert(
- dbSession,
- new PermissionTemplateDto()
- .setOrganizationUuid(organizationDto.getUuid())
- .setUuid(uuidFactory.create())
- .setName(PERM_TEMPLATE_NAME)
- .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName()))
- .setCreatedAt(now)
- .setUpdatedAt(now));
-
- insertGroupPermission(dbSession, permissionTemplateDto, ADMIN, ownerGroup);
- insertGroupPermission(dbSession, permissionTemplateDto, ISSUE_ADMIN, ownerGroup);
- insertGroupPermission(dbSession, permissionTemplateDto, SCAN.getKey(), ownerGroup);
- insertGroupPermission(dbSession, permissionTemplateDto, USER, defaultGroup);
- insertGroupPermission(dbSession, permissionTemplateDto, CODEVIEWER, defaultGroup);
-
- dbClient.organizationDao().setDefaultTemplates(
- dbSession,
- organizationDto.getUuid(),
- new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid()));
- }
-
- private void insertPersonalOrgDefaultTemplate(DbSession dbSession, OrganizationDto organizationDto, GroupDto defaultGroup) {
- long now = system2.now();
- Date dateNow = new Date(now);
- PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert(
- dbSession,
- new PermissionTemplateDto()
- .setOrganizationUuid(organizationDto.getUuid())
- .setUuid(uuidFactory.create())
- .setName("Default template")
- .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName()))
- .setCreatedAt(dateNow)
- .setUpdatedAt(dateNow));
-
- insertProjectCreatorPermission(dbSession, permissionTemplateDto, ADMIN, now);
- insertProjectCreatorPermission(dbSession, permissionTemplateDto, ISSUE_ADMIN, now);
- insertProjectCreatorPermission(dbSession, permissionTemplateDto, SCAN.getKey(), now);
- insertGroupPermission(dbSession, permissionTemplateDto, USER, defaultGroup);
- insertGroupPermission(dbSession, permissionTemplateDto, CODEVIEWER, defaultGroup);
-
- dbClient.organizationDao().setDefaultTemplates(
- dbSession,
- organizationDto.getUuid(),
- new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid()));
- }
-
- private void insertProjectCreatorPermission(DbSession dbSession, PermissionTemplateDto permissionTemplateDto, String permission, long now) {
- dbClient.permissionTemplateCharacteristicDao().insert(
- dbSession,
- new PermissionTemplateCharacteristicDto()
- .setTemplateId(permissionTemplateDto.getId())
- .setWithProjectCreator(true)
- .setPermission(permission)
- .setCreatedAt(now)
- .setUpdatedAt(now));
- }
-
- private void insertGroupPermission(DbSession dbSession, PermissionTemplateDto template, String permission, @Nullable GroupDto group) {
- dbClient.permissionTemplateDao().insertGroupPermission(dbSession, template.getId(), group == null ? null : group.getId(), permission);
- }
-
- private void insertQualityProfiles(DbSession dbSession, DbSession batchDbSession, OrganizationDto organization) {
- Map<QProfileName, BuiltInQProfile> builtInsPerName = builtInQProfileRepository.get().stream()
- .collect(uniqueIndex(BuiltInQProfile::getQProfileName));
-
- List<DefaultQProfileDto> defaults = new ArrayList<>();
- dbClient.qualityProfileDao().selectBuiltInRulesProfiles(dbSession).forEach(rulesProfile -> {
- OrgQProfileDto dto = new OrgQProfileDto()
- .setOrganizationUuid(organization.getUuid())
- .setRulesProfileUuid(rulesProfile.getKee())
- .setUuid(uuidFactory.create());
-
- QProfileName name = new QProfileName(rulesProfile.getLanguage(), rulesProfile.getName());
- BuiltInQProfile builtIn = builtInsPerName.get(name);
- if (builtIn != null && builtIn.isDefault()) {
- // rows of table default_qprofiles must be inserted after org_qprofiles
- // in order to benefit from batch SQL inserts
- defaults.add(new DefaultQProfileDto()
- .setQProfileUuid(dto.getUuid())
- .setOrganizationUuid(organization.getUuid())
- .setLanguage(builtIn.getLanguage()));
- }
-
- dbClient.qualityProfileDao().insert(batchDbSession, dto);
- });
-
- defaults.forEach(defaultQProfileDto -> dbClient.defaultQProfileDao().insertOrUpdate(dbSession, defaultQProfileDto));
- }
-
- /**
- * Owners group has an hard coded name, a description based on the organization's name and has all global permissions.
- */
- private GroupDto insertOwnersGroup(DbSession dbSession, OrganizationDto organization) {
- GroupDto group = dbClient.groupDao().insert(dbSession, new GroupDto()
- .setOrganizationUuid(organization.getUuid())
- .setName(OWNERS_GROUP_NAME)
- .setDescription(format(OWNERS_GROUP_DESCRIPTION_PATTERN, organization.getName())));
- OrganizationPermission.all().forEach(p -> addPermissionToGroup(dbSession, group, p));
- return group;
- }
-
- private void addPermissionToGroup(DbSession dbSession, GroupDto group, OrganizationPermission permission) {
- dbClient.groupPermissionDao().insert(
- dbSession,
- new GroupPermissionDto()
- .setOrganizationUuid(group.getOrganizationUuid())
- .setGroupId(group.getId())
- .setRole(permission.getKey()));
- }
-
- private void insertUserPermissions(DbSession dbSession, UserDto userDto, OrganizationDto organization, OrganizationPermission permission) {
- dbClient.userPermissionDao().insert(
- dbSession,
- new UserPermissionDto(organization.getUuid(), permission.getKey(), userDto.getId(), null));
- }
-
- private void addCurrentUserToGroup(DbSession dbSession, GroupDto group, int createUserId) {
- dbClient.userGroupDao().insert(
- dbSession,
- new UserGroupDto().setGroupId(group.getId()).setUserId(createUserId));
- }
-
- private void insertOrganizationMember(DbSession dbSession, OrganizationDto organizationDto, int userId) {
- dbClient.organizationMemberDao().insert(dbSession, new OrganizationMemberDto()
- .setOrganizationUuid(organizationDto.getUuid())
- .setUserId(userId));
- }
-}
--- /dev/null
+/*
+ * 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.organization;
+
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.usergroups.DefaultGroupCreatorImpl;
+
+import static java.util.Objects.requireNonNull;
+
+public interface OrganizationUpdater {
+ String OWNERS_GROUP_NAME = "Owners";
+ String OWNERS_GROUP_DESCRIPTION_PATTERN = "Owners of organization %s";
+ String PERM_TEMPLATE_NAME = "Default template";
+ String PERM_TEMPLATE_DESCRIPTION_PATTERN = "Default permission template of organization %s";
+ String PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN = "%s's personal organization";
+
+ /**
+ * Create a new non guarded organization with the specified properties and of which the specified user will assign
+ * Administer Organization permission.
+ * <p>
+ * This method does several operations at once:
+ * <ol>
+ * <li>create an ungarded organization with the specified details</li>
+ * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with all organization wide permissions</li>
+ * <li>create a group called {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} with browse permissions</li>
+ * <li>make the specified user a member of these groups</li>
+ * <li>create a default template for the organization
+ * <ul>
+ * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li>
+ * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li>
+ * </ul>
+ * </li>
+ * <li>this permission template defines the specified permissions (which effectively makes projects public):
+ * <ul>
+ * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ADMIN ADMIN}</li>
+ * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li>
+ * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li>
+ * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#USER USER}</li>
+ * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
+ * </ul>
+ * </li>
+ * </ol>
+ * </p>
+ *
+ * @return the created organization
+ *
+ * @throws KeyConflictException if an organization with the specified key already exists
+ * @throws IllegalArgumentException if any field of {@code newOrganization} is invalid according to {@link OrganizationValidation}
+ */
+ OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization) throws KeyConflictException;
+
+ /**
+ * Create a new guarded organization which details are based on the login of the specified User.
+ * <p>
+ * This method does several operations at once:
+ * <ol>
+ * <li>
+ * create a guarded organization with the details computed from user's details:
+ * <ul>
+ * <li>key: generated from the user's login</li>
+ * <li>name: the user's name if set, otherwise the user's login</li>
+ * <li>description: {@link #PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN "[name]'s personal organization"} where name
+ * is user name (when non null and non empty) or login</li>
+ * <li>url and avatar: null</li>
+ * </ul>
+ * </li>
+ * <li>create a group called {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} with browse permissions</li>
+ * <li>make the specified user a member of this group</li>
+ * <li>give all organization wide permissions to the user</li>
+ * <li>create a default template for the organization
+ * <ul>
+ * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li>
+ * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li>
+ * </ul>
+ * </li>
+ * <li>this permission template defines the specified permissions (which effectively makes projects public and
+ * automatically adds new projects to the user's favorites):
+ * <ul>
+ * <li>project creator : {@link UserRole#ADMIN ADMIN}</li>
+ * <li>project creator : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li>
+ * <li>project creator : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li>
+ * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#USER USER}</li>
+ * <li>group {@link DefaultGroupCreatorImpl#DEFAULT_GROUP_NAME members} : {@link UserRole#CODEVIEWER CODEVIEWER}</li>
+ * </ul>
+ * </li>
+ * </ol>
+ * </p>
+ *
+ * @return the created organization or empty if feature is disabled
+ *
+ * @throws IllegalArgumentException if any field of {@code newOrganization} is invalid according to {@link OrganizationValidation}
+ * @throws IllegalStateException if an organization with the key generated from the login already exists
+ */
+ Optional<OrganizationDto> createForUser(DbSession dbSession, UserDto newUser);
+
+ /**
+ * Update the personal organization key of a user.
+ * No update will be performed if generated key match the same key as existing one.
+ *
+ * @throws IllegalStateException if user has no no personal organization
+ * @throws IllegalStateException if personal organization uuid does not exist
+ * @throws IllegalStateException if an organization with the key generated from the login already exists
+ */
+ void updateOrganizationKey(DbSession dbSession, OrganizationDto organization, String newKey);
+
+ final class KeyConflictException extends Exception {
+ KeyConflictException(String message) {
+ super(message);
+ }
+ }
+
+ final class NewOrganization {
+ private final String key;
+ private final String name;
+ @CheckForNull
+ private final String description;
+ @CheckForNull
+ private final String url;
+ @CheckForNull
+ private final String avatar;
+
+ private NewOrganization(Builder builder) {
+ this.key = builder.key;
+ this.name = builder.name;
+ this.description = builder.description;
+ this.url = builder.url;
+ this.avatar = builder.avatarUrl;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @CheckForNull
+ public String getDescription() {
+ return description;
+ }
+
+ @CheckForNull
+ public String getUrl() {
+ return url;
+ }
+
+ @CheckForNull
+ public String getAvatar() {
+ return avatar;
+ }
+
+ public static NewOrganization.Builder newOrganizationBuilder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+ private String key;
+ private String name;
+ private String description;
+ private String url;
+ private String avatarUrl;
+
+ private Builder() {
+ // use factory method
+ }
+
+ public Builder setKey(String key) {
+ this.key = requireNonNull(key, "key can't be null");
+ return this;
+ }
+
+ public Builder setName(String name) {
+ this.name = requireNonNull(name, "name can't be null");
+ return this;
+ }
+
+ public Builder setDescription(@Nullable String description) {
+ this.description = description;
+ return this;
+ }
+
+ public Builder setUrl(@Nullable String url) {
+ this.url = url;
+ return this;
+ }
+
+ public Builder setAvatarUrl(@Nullable String avatarUrl) {
+ this.avatarUrl = avatarUrl;
+ return this;
+ }
+
+ public NewOrganization build() {
+ requireNonNull(key, "key can't be null");
+ requireNonNull(name, "name can't be null");
+ return new NewOrganization(this);
+ }
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.organization;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
+import org.sonar.core.config.CorePropertyDefinitions;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.organization.DefaultTemplates;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationMemberDto;
+import org.sonar.db.permission.GroupPermissionDto;
+import org.sonar.db.permission.OrganizationPermission;
+import org.sonar.db.permission.UserPermissionDto;
+import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto;
+import org.sonar.db.permission.template.PermissionTemplateDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.qualityprofile.DefaultQProfileDto;
+import org.sonar.db.qualityprofile.OrgQProfileDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserGroupDto;
+import org.sonar.server.qualityprofile.BuiltInQProfile;
+import org.sonar.server.qualityprofile.BuiltInQProfileRepository;
+import org.sonar.server.qualityprofile.QProfileName;
+import org.sonar.server.user.index.UserIndexer;
+import org.sonar.server.usergroups.DefaultGroupCreator;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.api.web.UserRole.ADMIN;
+import static org.sonar.api.web.UserRole.CODEVIEWER;
+import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
+import static org.sonar.api.web.UserRole.USER;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.db.permission.OrganizationPermission.SCAN;
+import static org.sonar.server.organization.OrganizationUpdater.NewOrganization.newOrganizationBuilder;
+
+public class OrganizationUpdaterImpl implements OrganizationUpdater {
+
+ private final DbClient dbClient;
+ private final System2 system2;
+ private final UuidFactory uuidFactory;
+ private final OrganizationValidation organizationValidation;
+ private final Configuration config;
+ private final BuiltInQProfileRepository builtInQProfileRepository;
+ private final DefaultGroupCreator defaultGroupCreator;
+ private final UserIndexer userIndexer;
+
+ public OrganizationUpdaterImpl(DbClient dbClient, System2 system2, UuidFactory uuidFactory,
+ OrganizationValidation organizationValidation, Configuration config, UserIndexer userIndexer,
+ BuiltInQProfileRepository builtInQProfileRepository, DefaultGroupCreator defaultGroupCreator) {
+ this.dbClient = dbClient;
+ this.system2 = system2;
+ this.uuidFactory = uuidFactory;
+ this.organizationValidation = organizationValidation;
+ this.config = config;
+ this.userIndexer = userIndexer;
+ this.builtInQProfileRepository = builtInQProfileRepository;
+ this.defaultGroupCreator = defaultGroupCreator;
+ }
+
+ @Override
+ public OrganizationDto create(DbSession dbSession, UserDto userCreator, NewOrganization newOrganization) throws KeyConflictException {
+ validate(newOrganization);
+ String key = newOrganization.getKey();
+ if (organizationKeyIsUsed(dbSession, key)) {
+ throw new KeyConflictException(format("Organization key '%s' is already used", key));
+ }
+
+ QualityGateDto builtInQualityGate = dbClient.qualityGateDao().selectBuiltIn(dbSession);
+ OrganizationDto organization = insertOrganization(dbSession, newOrganization, builtInQualityGate);
+ insertOrganizationMember(dbSession, organization, userCreator.getId());
+ dbClient.qualityGateDao().associate(dbSession, uuidFactory.create(), organization, builtInQualityGate);
+ GroupDto ownerGroup = insertOwnersGroup(dbSession, organization);
+ GroupDto defaultGroup = defaultGroupCreator.create(dbSession, organization.getUuid());
+ insertDefaultTemplateOnGroups(dbSession, organization, ownerGroup, defaultGroup);
+ try (DbSession batchDbSession = dbClient.openSession(true)) {
+ insertQualityProfiles(dbSession, batchDbSession, organization);
+ addCurrentUserToGroup(dbSession, ownerGroup, userCreator.getId());
+ addCurrentUserToGroup(dbSession, defaultGroup, userCreator.getId());
+
+ batchDbSession.commit();
+
+ // Elasticsearch is updated when DB session is committed
+ userIndexer.commitAndIndex(dbSession, userCreator);
+
+ return organization;
+ }
+ }
+
+ @Override
+ public Optional<OrganizationDto> createForUser(DbSession dbSession, UserDto newUser) {
+ if (!isCreatePersonalOrgEnabled()) {
+ return Optional.empty();
+ }
+
+ String nameOrLogin = nameOrLogin(newUser);
+ NewOrganization newOrganization = newOrganizationBuilder()
+ .setKey(organizationValidation.generateKeyFrom(newUser.getLogin()))
+ .setName(toName(nameOrLogin))
+ .setDescription(format(PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN, nameOrLogin))
+ .build();
+ checkKey(dbSession, newOrganization.getKey());
+
+ QualityGateDto builtInQualityGate = dbClient.qualityGateDao().selectBuiltIn(dbSession);
+ OrganizationDto organization = insertOrganization(dbSession, newOrganization, builtInQualityGate,
+ dto -> dto.setGuarded(true));
+ dbClient.userDao().update(dbSession, newUser.setOrganizationUuid(organization.getUuid()));
+ insertOrganizationMember(dbSession, organization, newUser.getId());
+ GroupDto defaultGroup = defaultGroupCreator.create(dbSession, organization.getUuid());
+ dbClient.qualityGateDao().associate(dbSession, uuidFactory.create(), organization, builtInQualityGate);
+ OrganizationPermission.all()
+ .forEach(p -> insertUserPermissions(dbSession, newUser, organization, p));
+ insertPersonalOrgDefaultTemplate(dbSession, organization, defaultGroup);
+ try (DbSession batchDbSession = dbClient.openSession(true)) {
+ insertQualityProfiles(dbSession, batchDbSession, organization);
+ addCurrentUserToGroup(dbSession, defaultGroup, newUser.getId());
+
+ batchDbSession.commit();
+
+ // Elasticsearch is updated when DB session is committed
+ userIndexer.commitAndIndex(dbSession, newUser);
+
+ return Optional.of(organization);
+ }
+ }
+
+ @Override
+ public void updateOrganizationKey(DbSession dbSession, OrganizationDto organization, String newKey) {
+ String sanitizedKey = organizationValidation.generateKeyFrom(newKey);
+ if (organization.getKey().equals(sanitizedKey)) {
+ return;
+ }
+ checkKey(dbSession, sanitizedKey);
+ dbClient.organizationDao().update(dbSession, organization.setKey(sanitizedKey));
+ }
+
+ private void checkKey(DbSession dbSession, String key) {
+ checkState(!organizationKeyIsUsed(dbSession, key),
+ "Can't create organization with key '%s' because an organization with this key already exists", key);
+ }
+
+ private static String nameOrLogin(UserDto newUser) {
+ String name = newUser.getName();
+ if (name == null || name.isEmpty()) {
+ return newUser.getLogin();
+ }
+ return name;
+ }
+
+ private String toName(String login) {
+ String name = login.substring(0, Math.min(login.length(), OrganizationValidation.NAME_MAX_LENGTH));
+ // should not happen has login can't be less than 2 chars, but we call it for safety
+ organizationValidation.checkName(name);
+ return name;
+ }
+
+ private boolean isCreatePersonalOrgEnabled() {
+ return config.getBoolean(CorePropertyDefinitions.ORGANIZATIONS_CREATE_PERSONAL_ORG).orElse(false);
+ }
+
+ private void validate(NewOrganization newOrganization) {
+ requireNonNull(newOrganization, "newOrganization can't be null");
+ organizationValidation.checkName(newOrganization.getName());
+ organizationValidation.checkKey(newOrganization.getKey());
+ organizationValidation.checkDescription(newOrganization.getDescription());
+ organizationValidation.checkUrl(newOrganization.getUrl());
+ organizationValidation.checkAvatar(newOrganization.getAvatar());
+ }
+
+ private OrganizationDto insertOrganization(DbSession dbSession, NewOrganization newOrganization, QualityGateDto builtInQualityGate, Consumer<OrganizationDto>... extendCreation) {
+ OrganizationDto res = new OrganizationDto()
+ .setUuid(uuidFactory.create())
+ .setName(newOrganization.getName())
+ .setKey(newOrganization.getKey())
+ .setDescription(newOrganization.getDescription())
+ .setUrl(newOrganization.getUrl())
+ .setDefaultQualityGateUuid(builtInQualityGate.getUuid())
+ .setAvatarUrl(newOrganization.getAvatar());
+ Arrays.stream(extendCreation).forEach(c -> c.accept(res));
+ dbClient.organizationDao().insert(dbSession, res, false);
+ return res;
+ }
+
+ private boolean organizationKeyIsUsed(DbSession dbSession, String key) {
+ return dbClient.organizationDao().selectByKey(dbSession, key).isPresent();
+ }
+
+ private void insertDefaultTemplateOnGroups(DbSession dbSession, OrganizationDto organizationDto, GroupDto ownerGroup, GroupDto defaultGroup) {
+ Date now = new Date(system2.now());
+ PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert(
+ dbSession,
+ new PermissionTemplateDto()
+ .setOrganizationUuid(organizationDto.getUuid())
+ .setUuid(uuidFactory.create())
+ .setName(PERM_TEMPLATE_NAME)
+ .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName()))
+ .setCreatedAt(now)
+ .setUpdatedAt(now));
+
+ insertGroupPermission(dbSession, permissionTemplateDto, ADMIN, ownerGroup);
+ insertGroupPermission(dbSession, permissionTemplateDto, ISSUE_ADMIN, ownerGroup);
+ insertGroupPermission(dbSession, permissionTemplateDto, SCAN.getKey(), ownerGroup);
+ insertGroupPermission(dbSession, permissionTemplateDto, USER, defaultGroup);
+ insertGroupPermission(dbSession, permissionTemplateDto, CODEVIEWER, defaultGroup);
+
+ dbClient.organizationDao().setDefaultTemplates(
+ dbSession,
+ organizationDto.getUuid(),
+ new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid()));
+ }
+
+ private void insertPersonalOrgDefaultTemplate(DbSession dbSession, OrganizationDto organizationDto, GroupDto defaultGroup) {
+ long now = system2.now();
+ Date dateNow = new Date(now);
+ PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert(
+ dbSession,
+ new PermissionTemplateDto()
+ .setOrganizationUuid(organizationDto.getUuid())
+ .setUuid(uuidFactory.create())
+ .setName("Default template")
+ .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName()))
+ .setCreatedAt(dateNow)
+ .setUpdatedAt(dateNow));
+
+ insertProjectCreatorPermission(dbSession, permissionTemplateDto, ADMIN, now);
+ insertProjectCreatorPermission(dbSession, permissionTemplateDto, ISSUE_ADMIN, now);
+ insertProjectCreatorPermission(dbSession, permissionTemplateDto, SCAN.getKey(), now);
+ insertGroupPermission(dbSession, permissionTemplateDto, USER, defaultGroup);
+ insertGroupPermission(dbSession, permissionTemplateDto, CODEVIEWER, defaultGroup);
+
+ dbClient.organizationDao().setDefaultTemplates(
+ dbSession,
+ organizationDto.getUuid(),
+ new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid()));
+ }
+
+ private void insertProjectCreatorPermission(DbSession dbSession, PermissionTemplateDto permissionTemplateDto, String permission, long now) {
+ dbClient.permissionTemplateCharacteristicDao().insert(
+ dbSession,
+ new PermissionTemplateCharacteristicDto()
+ .setTemplateId(permissionTemplateDto.getId())
+ .setWithProjectCreator(true)
+ .setPermission(permission)
+ .setCreatedAt(now)
+ .setUpdatedAt(now));
+ }
+
+ private void insertGroupPermission(DbSession dbSession, PermissionTemplateDto template, String permission, @Nullable GroupDto group) {
+ dbClient.permissionTemplateDao().insertGroupPermission(dbSession, template.getId(), group == null ? null : group.getId(), permission);
+ }
+
+ private void insertQualityProfiles(DbSession dbSession, DbSession batchDbSession, OrganizationDto organization) {
+ Map<QProfileName, BuiltInQProfile> builtInsPerName = builtInQProfileRepository.get().stream()
+ .collect(uniqueIndex(BuiltInQProfile::getQProfileName));
+
+ List<DefaultQProfileDto> defaults = new ArrayList<>();
+ dbClient.qualityProfileDao().selectBuiltInRulesProfiles(dbSession).forEach(rulesProfile -> {
+ OrgQProfileDto dto = new OrgQProfileDto()
+ .setOrganizationUuid(organization.getUuid())
+ .setRulesProfileUuid(rulesProfile.getKee())
+ .setUuid(uuidFactory.create());
+
+ QProfileName name = new QProfileName(rulesProfile.getLanguage(), rulesProfile.getName());
+ BuiltInQProfile builtIn = builtInsPerName.get(name);
+ if (builtIn != null && builtIn.isDefault()) {
+ // rows of table default_qprofiles must be inserted after org_qprofiles
+ // in order to benefit from batch SQL inserts
+ defaults.add(new DefaultQProfileDto()
+ .setQProfileUuid(dto.getUuid())
+ .setOrganizationUuid(organization.getUuid())
+ .setLanguage(builtIn.getLanguage()));
+ }
+
+ dbClient.qualityProfileDao().insert(batchDbSession, dto);
+ });
+
+ defaults.forEach(defaultQProfileDto -> dbClient.defaultQProfileDao().insertOrUpdate(dbSession, defaultQProfileDto));
+ }
+
+ /**
+ * Owners group has an hard coded name, a description based on the organization's name and has all global permissions.
+ */
+ private GroupDto insertOwnersGroup(DbSession dbSession, OrganizationDto organization) {
+ GroupDto group = dbClient.groupDao().insert(dbSession, new GroupDto()
+ .setOrganizationUuid(organization.getUuid())
+ .setName(OWNERS_GROUP_NAME)
+ .setDescription(format(OWNERS_GROUP_DESCRIPTION_PATTERN, organization.getName())));
+ OrganizationPermission.all().forEach(p -> addPermissionToGroup(dbSession, group, p));
+ return group;
+ }
+
+ private void addPermissionToGroup(DbSession dbSession, GroupDto group, OrganizationPermission permission) {
+ dbClient.groupPermissionDao().insert(
+ dbSession,
+ new GroupPermissionDto()
+ .setOrganizationUuid(group.getOrganizationUuid())
+ .setGroupId(group.getId())
+ .setRole(permission.getKey()));
+ }
+
+ private void insertUserPermissions(DbSession dbSession, UserDto userDto, OrganizationDto organization, OrganizationPermission permission) {
+ dbClient.userPermissionDao().insert(
+ dbSession,
+ new UserPermissionDto(organization.getUuid(), permission.getKey(), userDto.getId(), null));
+ }
+
+ private void addCurrentUserToGroup(DbSession dbSession, GroupDto group, int createUserId) {
+ dbClient.userGroupDao().insert(
+ dbSession,
+ new UserGroupDto().setGroupId(group.getId()).setUserId(createUserId));
+ }
+
+ private void insertOrganizationMember(DbSession dbSession, OrganizationDto organizationDto, int userId) {
+ dbClient.organizationMemberDao().insert(dbSession, new OrganizationMemberDto()
+ .setOrganizationUuid(organizationDto.getUuid())
+ .setUserId(userId));
+ }
+}
import org.sonar.db.DbSession;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.user.UserDto;
-import org.sonar.server.organization.OrganizationCreation;
import org.sonar.server.organization.OrganizationFlags;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.OrganizationValidation;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Organizations.CreateWsResponse;
import static com.google.common.base.Preconditions.checkArgument;
-import static org.sonar.server.organization.OrganizationCreation.NewOrganization.newOrganizationBuilder;
+import static org.sonar.server.organization.OrganizationUpdater.NewOrganization.newOrganizationBuilder;
import static org.sonar.server.organization.OrganizationValidation.KEY_MAX_LENGTH;
import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_KEY;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
private final DbClient dbClient;
private final OrganizationsWsSupport wsSupport;
private final OrganizationValidation organizationValidation;
- private final OrganizationCreation organizationCreation;
+ private final OrganizationUpdater organizationUpdater;
private final OrganizationFlags organizationFlags;
public CreateAction(Configuration config, UserSession userSession, DbClient dbClient, OrganizationsWsSupport wsSupport,
- OrganizationValidation organizationValidation, OrganizationCreation organizationCreation, OrganizationFlags organizationFlags) {
+ OrganizationValidation organizationValidation, OrganizationUpdater organizationUpdater, OrganizationFlags organizationFlags) {
this.config = config;
this.userSession = userSession;
this.dbClient = dbClient;
this.wsSupport = wsSupport;
this.organizationValidation = organizationValidation;
- this.organizationCreation = organizationCreation;
+ this.organizationUpdater = organizationUpdater;
this.organizationFlags = organizationFlags;
}
try (DbSession dbSession = dbClient.openSession(false)) {
organizationFlags.checkEnabled(dbSession);
UserDto currentUser = dbClient.userDao().selectActiveUserByLogin(dbSession, userSession.getLogin());
- OrganizationDto organization = organizationCreation.create(
+ OrganizationDto organization = organizationUpdater.create(
dbSession,
currentUser,
newOrganizationBuilder()
.build());
writeResponse(request, response, organization);
- } catch (OrganizationCreation.KeyConflictException e) {
+ } catch (OrganizationUpdater.KeyConflictException e) {
checkArgument(requestKey == null, "Key '%s' is already used. Specify another one.", key);
checkArgument(requestKey != null, "Key '%s' generated from name '%s' is already used. Specify one.", key, name);
}
import org.sonar.server.notification.NotificationModule;
import org.sonar.server.notification.ws.NotificationWsModule;
import org.sonar.server.organization.BillingValidationsProxyImpl;
-import org.sonar.server.organization.OrganizationCreationImpl;
+import org.sonar.server.organization.OrganizationUpdaterImpl;
import org.sonar.server.organization.OrganizationValidationImpl;
import org.sonar.server.organization.ws.OrganizationsWsModule;
import org.sonar.server.permission.GroupPermissionChanger;
// organizations
OrganizationValidationImpl.class,
- OrganizationCreationImpl.class,
+ OrganizationUpdaterImpl.class,
OrganizationsWsModule.class,
BillingValidationsProxyImpl.class,
import org.sonar.db.user.UserGroupDto;
import org.sonar.server.authentication.LocalAuthentication;
import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationCreation;
import org.sonar.server.organization.OrganizationFlags;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.user.index.UserIndexer;
import org.sonar.server.usergroups.DefaultGroupFinder;
import org.sonar.server.util.Validation;
private final UserIndexer userIndexer;
private final OrganizationFlags organizationFlags;
private final DefaultOrganizationProvider defaultOrganizationProvider;
- private final OrganizationCreation organizationCreation;
+ private final OrganizationUpdater organizationUpdater;
private final DefaultGroupFinder defaultGroupFinder;
private final Configuration config;
private final LocalAuthentication localAuthentication;
public UserUpdater(NewUserNotifier newUserNotifier, DbClient dbClient, UserIndexer userIndexer, OrganizationFlags organizationFlags,
- DefaultOrganizationProvider defaultOrganizationProvider, OrganizationCreation organizationCreation, DefaultGroupFinder defaultGroupFinder, Configuration config,
- LocalAuthentication localAuthentication) {
+ DefaultOrganizationProvider defaultOrganizationProvider, OrganizationUpdater organizationUpdater, DefaultGroupFinder defaultGroupFinder, Configuration config,
+ LocalAuthentication localAuthentication) {
this.newUserNotifier = newUserNotifier;
this.dbClient = dbClient;
this.userIndexer = userIndexer;
this.organizationFlags = organizationFlags;
this.defaultOrganizationProvider = defaultOrganizationProvider;
- this.organizationCreation = organizationCreation;
+ this.organizationUpdater = organizationUpdater;
this.defaultGroupFinder = defaultGroupFinder;
this.config = config;
this.localAuthentication = localAuthentication;
userDto.setActive(true);
UserDto res = dbClient.userDao().insert(dbSession, userDto);
addUserToDefaultOrganizationAndDefaultGroup(dbSession, userDto);
- organizationCreation.createForUser(dbSession, userDto);
+ organizationUpdater.createForUser(dbSession, userDto);
return res;
}
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.sonar.api.platform.Server;
import org.sonar.api.server.authentication.BaseIdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.user.TestUserSessionFactory;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSession;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.FORBID;
public class BaseContextFactoryTest {
private static final String PUBLIC_ROOT_URL = "https://mydomain.com";
private static final UserIdentity USER_IDENTITY = UserIdentity.builder()
+ .setProviderId("ABCD")
.setProviderLogin("johndoo")
.setLogin("id:johndoo")
.setName("John")
.setEmail("john@email.com")
.build();
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class);
- private UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
+ private TestUserIdentityAuthenticator userIdentityAuthenticator = new TestUserIdentityAuthenticator();
private Server server = mock(Server.class);
private HttpServletRequest request = mock(HttpServletRequest.class);
@Before
public void setUp() throws Exception {
when(server.getPublicRootUrl()).thenReturn(PUBLIC_ROOT_URL);
- when(identityProvider.getName()).thenReturn("provIdeur Nameuh");
+ when(identityProvider.getName()).thenReturn("GitHub");
+ when(identityProvider.getKey()).thenReturn("github");
when(request.getSession()).thenReturn(mock(HttpSession.class));
}
@Test
public void authenticate() {
- UserDto userDto = dbTester.users().insertUser();
- when(userIdentityAuthenticator.authenticate(USER_IDENTITY, identityProvider, Source.external(identityProvider), FORBID)).thenReturn(userDto);
BaseIdentityProvider.Context context = underTest.newContext(request, response, identityProvider);
+ ArgumentCaptor<UserDto> userArgumentCaptor = ArgumentCaptor.forClass(UserDto.class);
context.authenticate(USER_IDENTITY);
- verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider, Source.external(identityProvider), FORBID);
- verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(request), eq(response));
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
verify(threadLocalUserSession).set(any(UserSession.class));
+ verify(jwtHttpHandler).generateToken(userArgumentCaptor.capture(), eq(request), eq(response));
+ assertThat(userArgumentCaptor.getValue().getLogin()).isEqualTo(USER_IDENTITY.getLogin());
+ assertThat(userArgumentCaptor.getValue().getExternalId()).isEqualTo(USER_IDENTITY.getProviderId());
+ assertThat(userArgumentCaptor.getValue().getExternalLogin()).isEqualTo(USER_IDENTITY.getProviderLogin());
+ assertThat(userArgumentCaptor.getValue().getExternalIdentityProvider()).isEqualTo("github");
}
}
import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
@Override
public void init(Context context) {
- throw new EmailAlreadyExistsException(existingUser.getEmail(), existingUser, UserIdentity.builder()
+ throw new EmailAlreadyExistsRedirectionException(existingUser.getEmail(), existingUser, UserIdentity.builder()
.setProviderLogin("john.github")
.setLogin("john.github")
.setName(existingUser.getName())
private HttpServletResponse response = mock(HttpServletResponse.class);
private HttpServletRequest request = mock(HttpServletRequest.class);
- private OAuth2AuthenticationParametersImpl underTest = new OAuth2AuthenticationParametersImpl();
+ private OAuth2AuthenticationParameters underTest = new OAuth2AuthenticationParametersImpl();
@Before
public void setUp() throws Exception {
}
@Test
- public void init_create_cookie_containing_parameters_from_request() {
+ public void init_create_cookie() {
when(request.getParameter("return_to")).thenReturn("/settings");
- when(request.getParameter("allowEmailShift")).thenReturn("true");
underTest.init(request, response);
public void init_does_not_create_cookie_when_parameters_are_empty() {
when(request.getParameter("return_to")).thenReturn("");
when(request.getParameter("allowEmailShift")).thenReturn("");
+ when(request.getParameter("allowUpdateLogin")).thenReturn("");
underTest.init(request, response);
public void init_does_not_create_cookie_when_parameters_are_null() {
when(request.getParameter("return_to")).thenReturn(null);
when(request.getParameter("allowEmailShift")).thenReturn(null);
+ when(request.getParameter("allowUpdateLogin")).thenReturn(null);
underTest.init(request, response);
assertThat(allowEmailShift).isEmpty();
}
+ @Test
+ public void getAllowUpdateLogin() {
+ when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"allowUpdateLogin\":\"true\"}")});
+
+ Optional<Boolean> allowLoginUpdate = underTest.getAllowUpdateLogin(request);
+
+ assertThat(allowLoginUpdate).isNotEmpty();
+ assertThat(allowLoginUpdate.get()).isTrue();
+ }
+
+ @Test
+ public void getAllowUpdateLogin_is_empty_when_no_cookie() {
+ when(request.getCookies()).thenReturn(new Cookie[] {});
+
+ Optional<Boolean> allowLoginUpdate = underTest.getAllowUpdateLogin(request);
+
+ assertThat(allowLoginUpdate).isEmpty();
+ }
+
+ @Test
+ public void getAllowUpdateLogin_is_empty_when_no_value() {
+ when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{}")});
+
+ Optional<Boolean> allowLoginUpdate = underTest.getAllowUpdateLogin(request);
+
+ assertThat(allowLoginUpdate).isEmpty();
+ }
+
@Test
public void delete() {
when(request.getCookies()).thenReturn(new Cookie[] {new Cookie(AUTHENTICATION_COOKIE_NAME, "{\"return_to\":\"/settings\"}")});
import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
@Override
public void callback(CallbackContext context) {
- throw new EmailAlreadyExistsException(existingUser.getEmail(), existingUser, UserIdentity.builder()
+ throw new EmailAlreadyExistsRedirectionException(existingUser.getEmail(), existingUser, UserIdentity.builder()
.setProviderLogin("john.github")
.setLogin("john.github")
.setName(existingUser.getName())
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
import org.sonar.api.platform.Server;
import org.sonar.api.server.authentication.OAuth2IdentityProvider;
import org.sonar.api.server.authentication.UserIdentity;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
import org.sonar.server.user.TestUserSessionFactory;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.user.UserSession;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.ALLOW;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.WARN;
-import static org.sonar.server.authentication.event.AuthenticationEvent.Source;
public class OAuth2ContextFactoryTest {
private static final String SECURED_PUBLIC_ROOT_URL = "https://mydomain.com";
private static final String PROVIDER_NAME = "provider name";
private static final UserIdentity USER_IDENTITY = UserIdentity.builder()
+ .setProviderId("ABCD")
.setProviderLogin("johndoo")
.setLogin("id:johndoo")
.setName("John")
@Rule
public ExpectedException thrown = ExpectedException.none();
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
private ThreadLocalUserSession threadLocalUserSession = mock(ThreadLocalUserSession.class);
- private UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
+ private TestUserIdentityAuthenticator userIdentityAuthenticator = new TestUserIdentityAuthenticator();
private Server server = mock(Server.class);
private OAuthCsrfVerifier csrfVerifier = mock(OAuthCsrfVerifier.class);
private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
@Test
public void authenticate() {
- UserDto userDto = dbTester.users().insertUser();
- when(userIdentityAuthenticator.authenticate(USER_IDENTITY, identityProvider, Source.oauth2(identityProvider), WARN)).thenReturn(userDto);
OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
callback.authenticate(USER_IDENTITY);
- verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider, Source.oauth2(identityProvider), WARN);
- verify(jwtHttpHandler).generateToken(any(UserDto.class), eq(request), eq(response));
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
verify(threadLocalUserSession).set(any(UserSession.class));
+ ArgumentCaptor<UserDto> userArgumentCaptor = ArgumentCaptor.forClass(UserDto.class);
+ verify(jwtHttpHandler).generateToken(userArgumentCaptor.capture(), eq(request), eq(response));
+ assertThat(userArgumentCaptor.getValue().getLogin()).isEqualTo(USER_IDENTITY.getLogin());
+ assertThat(userArgumentCaptor.getValue().getExternalId()).isEqualTo(USER_IDENTITY.getProviderId());
+ assertThat(userArgumentCaptor.getValue().getExternalLogin()).isEqualTo(USER_IDENTITY.getProviderLogin());
+ assertThat(userArgumentCaptor.getValue().getExternalIdentityProvider()).isEqualTo(PROVIDER_KEY);
}
@Test
public void authenticate_with_allow_email_shift() {
when(oAuthParameters.getAllowEmailShift(request)).thenReturn(Optional.of(true));
- UserDto userDto = dbTester.users().insertUser();
- when(userIdentityAuthenticator.authenticate(USER_IDENTITY, identityProvider, Source.oauth2(identityProvider), ALLOW)).thenReturn(userDto);
OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
callback.authenticate(USER_IDENTITY);
- verify(userIdentityAuthenticator).authenticate(USER_IDENTITY, identityProvider, Source.oauth2(identityProvider), ALLOW);
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(ExistingEmailStrategy.ALLOW);
+ }
+
+ @Test
+ public void authenticate_without_email_shift() {
+ when(oAuthParameters.getAllowEmailShift(request)).thenReturn(Optional.of(false));
+ OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
+
+ callback.authenticate(USER_IDENTITY);
+
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(ExistingEmailStrategy.WARN);
+ }
+
+ @Test
+ public void authenticate_with_allow_login_update() {
+ when(oAuthParameters.getAllowUpdateLogin(request)).thenReturn(Optional.of(true));
+ OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
+
+ callback.authenticate(USER_IDENTITY);
+
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUpdateLoginStrategy()).isEqualTo(UpdateLoginStrategy.ALLOW);
+ }
+
+ @Test
+ public void authenticate_without_allowing_login_update() {
+ when(oAuthParameters.getAllowUpdateLogin(request)).thenReturn(Optional.of(false));
+ OAuth2IdentityProvider.CallbackContext callback = newCallbackContext();
+
+ callback.authenticate(USER_IDENTITY);
+
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUpdateLoginStrategy()).isEqualTo(UpdateLoginStrategy.WARN);
}
@Test
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.security.Authenticator;
import org.sonar.api.security.ExternalGroupsProvider;
import org.sonar.api.security.ExternalUsersProvider;
import org.sonar.api.security.SecurityRealm;
import org.sonar.api.security.UserDetails;
-import org.sonar.api.server.authentication.IdentityProvider;
-import org.sonar.api.server.authentication.UserIdentity;
-import org.sonar.db.user.UserDto;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.user.SecurityRealmFactory;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.rules.ExpectedException.none;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static org.sonar.db.user.UserTesting.newUserDto;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.FORBID;
+import static org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy.FORBID;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC_TOKEN;
import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
private static final String LOGIN = "LOGIN";
private static final String PASSWORD = "PASSWORD";
- private static final UserDto USER = newUserDto();
private static final String REALM_NAME = "realm name";
@Rule
public ExpectedException expectedException = none();
- private ArgumentCaptor<UserIdentity> userIdentityArgumentCaptor = ArgumentCaptor.forClass(UserIdentity.class);
- private ArgumentCaptor<IdentityProvider> identityProviderArgumentCaptor = ArgumentCaptor.forClass(IdentityProvider.class);
- private ArgumentCaptor<AuthenticationEvent.Source> sourceCaptor = ArgumentCaptor.forClass(Source.class);
-
private MapSettings settings = new MapSettings();
private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
private ExternalUsersProvider externalUsersProvider = mock(ExternalUsersProvider.class);
private ExternalGroupsProvider externalGroupsProvider = mock(ExternalGroupsProvider.class);
- private UserIdentityAuthenticator userIdentityAuthenticator = mock(UserIdentityAuthenticator.class);
+ private TestUserIdentityAuthenticator userIdentityAuthenticator = new TestUserIdentityAuthenticator();
private AuthenticationEvent authenticationEvent = mock(AuthenticationEvent.class);
private HttpServletRequest request = mock(HttpServletRequest.class);
userDetails.setName("name");
userDetails.setEmail("email");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
- UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
- assertThat(userIdentity.getLogin()).isEqualTo(LOGIN);
- assertThat(userIdentity.getProviderLogin()).isEqualTo(LOGIN);
- assertThat(userIdentity.getName()).isEqualTo("name");
- assertThat(userIdentity.getEmail()).isEqualTo("email");
- assertThat(userIdentity.shouldSyncGroups()).isFalse();
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getExistingEmailStrategy()).isEqualTo(FORBID);
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getLogin()).isEqualTo(LOGIN);
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo(LOGIN);
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderId()).isNull();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo("name");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getEmail()).isEqualTo("email");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isFalse();
verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
}
userDetails.setName("name");
userDetails.setEmail("email");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
-
- assertThat(identityProviderArgumentCaptor.getValue().getKey()).isEqualTo("sonarqube");
- assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube");
- assertThat(identityProviderArgumentCaptor.getValue().getDisplay()).isNull();
- assertThat(identityProviderArgumentCaptor.getValue().isEnabled()).isTrue();
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getKey()).isEqualTo("sonarqube");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getDisplay()).isNull();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().isEnabled()).isTrue();
verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
}
UserDetails userDetails = new UserDetails();
userDetails.setEmail("email");
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
- assertThat(identityProviderArgumentCaptor.getValue().getName()).isEqualTo("sonarqube");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getProvider().getName()).isEqualTo("sonarqube");
verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
}
@Test
public void authenticate_with_group_sync() {
when(externalGroupsProvider.doGetGroups(any(ExternalGroupsProvider.Context.class))).thenReturn(asList("group1", "group2"));
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
executeStartWithGroupSync();
- executeAuthenticate();
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
+ executeAuthenticate();
- UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
- assertThat(userIdentity.shouldSyncGroups()).isTrue();
- assertThat(userIdentity.getGroups()).containsOnly("group1", "group2");
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().shouldSyncGroups()).isTrue();
verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
}
UserDetails userDetails = new UserDetails();
userDetails.setName(null);
when(externalUsersProvider.doGetUserDetails(any(ExternalUsersProvider.Context.class))).thenReturn(userDetails);
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
underTest.authenticate(LOGIN, PASSWORD, request, BASIC);
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
- assertThat(userIdentityArgumentCaptor.getValue().getName()).isEqualTo(LOGIN);
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getName()).isEqualTo(LOGIN);
verify(authenticationEvent).loginSuccess(request, LOGIN, Source.realm(BASIC, REALM_NAME));
}
@Test
public void use_downcase_login() {
settings.setProperty("sonar.authenticator.downcase", true);
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
executeStartWithoutGroupSync();
+
executeAuthenticate("LOGIN");
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
- UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
- assertThat(userIdentity.getLogin()).isEqualTo("login");
- assertThat(userIdentity.getProviderLogin()).isEqualTo("login");
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getLogin()).isEqualTo("login");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("login");
verify(authenticationEvent).loginSuccess(request, "login", Source.realm(BASIC, REALM_NAME));
}
@Test
public void does_not_user_downcase_login() {
settings.setProperty("sonar.authenticator.downcase", false);
- when(userIdentityAuthenticator.authenticate(any(UserIdentity.class), any(IdentityProvider.class), any(Source.class), eq(FORBID))).thenReturn(USER);
executeStartWithoutGroupSync();
+
executeAuthenticate("LoGiN");
- verify(userIdentityAuthenticator).authenticate(userIdentityArgumentCaptor.capture(), identityProviderArgumentCaptor.capture(), sourceCaptor.capture(), eq(FORBID));
- UserIdentity userIdentity = userIdentityArgumentCaptor.getValue();
- assertThat(userIdentity.getLogin()).isEqualTo("LoGiN");
- assertThat(userIdentity.getProviderLogin()).isEqualTo("LoGiN");
+ assertThat(userIdentityAuthenticator.isAuthenticated()).isTrue();
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getLogin()).isEqualTo("LoGiN");
+ assertThat(userIdentityAuthenticator.getAuthenticatorParameters().getUserIdentity().getProviderLogin()).isEqualTo("LoGiN");
verify(authenticationEvent).loginSuccess(request, "LoGiN", Source.realm(BASIC, REALM_NAME));
}
import org.sonar.server.authentication.event.AuthenticationEvent.Source;
import org.sonar.server.es.EsTester;
import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.user.NewUserNotifier;
private GroupDto sonarUsers;
private System2 system2 = mock(System2.class);
- private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
+ private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class);
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
- private UserIdentityAuthenticator userIdentityAuthenticator = new UserIdentityAuthenticator(
+ private UserIdentityAuthenticatorImpl userIdentityAuthenticator = new UserIdentityAuthenticatorImpl(
db.getDbClient(),
- new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, organizationCreation,
+ new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater,
new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication),
- defaultOrganizationProvider, organizationFlags, new DefaultGroupFinder(db.getDbClient()));
+ defaultOrganizationProvider, organizationFlags, mock(OrganizationUpdater.class), new DefaultGroupFinder(db.getDbClient()));
private HttpServletResponse response = mock(HttpServletResponse.class);
private JwtHttpHandler jwtHttpHandler = mock(JwtHttpHandler.class);
--- /dev/null
+/*
+ * 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.authentication;
+
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+
+public class TestUserIdentityAuthenticator implements UserIdentityAuthenticator {
+
+ private UserIdentityAuthenticatorParameters authenticatorParameters;
+
+ @Override
+ public UserDto authenticate(UserIdentityAuthenticatorParameters authenticatorParameters) {
+ this.authenticatorParameters = authenticatorParameters;
+ String providerId = authenticatorParameters.getUserIdentity().getProviderId();
+ return UserTesting.newUserDto()
+ .setLocal(false)
+ .setLogin(authenticatorParameters.getUserIdentity().getLogin())
+ .setExternalLogin(authenticatorParameters.getUserIdentity().getProviderLogin())
+ .setExternalId(providerId == null ? authenticatorParameters.getUserIdentity().getProviderLogin() : providerId)
+ .setExternalIdentityProvider(authenticatorParameters.getProvider().getKey());
+ }
+
+ boolean isAuthenticated() {
+ return authenticatorParameters != null;
+ }
+
+ UserIdentityAuthenticatorParameters getAuthenticatorParameters() {
+ return authenticatorParameters;
+ }
+}
--- /dev/null
+/*
+ * 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.authentication;
+
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.server.authentication.UserIdentity;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbTester;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy;
+import org.sonar.server.authentication.UserIdentityAuthenticatorParameters.UpdateLoginStrategy;
+import org.sonar.server.authentication.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationEvent.Source;
+import org.sonar.server.authentication.exception.EmailAlreadyExistsRedirectionException;
+import org.sonar.server.authentication.exception.UpdateLoginRedirectionException;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.organization.OrganizationUpdater;
+import org.sonar.server.organization.OrganizationUpdaterImpl;
+import org.sonar.server.organization.OrganizationValidationImpl;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
+import org.sonar.server.organization.TestOrganizationFlags;
+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 static com.google.common.collect.Sets.newHashSet;
+import static java.util.Arrays.stream;
+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;
+import static org.sonar.db.user.UserTesting.newUserDto;
+import static org.sonar.server.authentication.UserIdentityAuthenticatorParameters.ExistingEmailStrategy.FORBID;
+import static org.sonar.server.authentication.event.AuthenticationEvent.Method.BASIC;
+import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
+
+public class UserIdentityAuthenticatorImplTest {
+
+ private static String USER_LOGIN = "github-johndoo";
+
+ private static UserIdentity USER_IDENTITY = UserIdentity.builder()
+ .setProviderId("ABCD")
+ .setProviderLogin("johndoo")
+ .setLogin(USER_LOGIN)
+ .setName("John")
+ .setEmail("john@email.com")
+ .build();
+
+ private static TestIdentityProvider IDENTITY_PROVIDER = new TestIdentityProvider()
+ .setKey("github")
+ .setName("name of github")
+ .setEnabled(true)
+ .setAllowsUsersToSignUp(true);
+
+ private MapSettings settings = new MapSettings();
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public DbTester db = DbTester.create(new AlwaysIncreasingSystem2());
+ @Rule
+ public EsTester es = EsTester.create();
+ private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
+ private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
+ private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class);
+ private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
+ private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
+ private UserUpdater userUpdater = new UserUpdater(
+ mock(NewUserNotifier.class),
+ db.getDbClient(),
+ userIndexer,
+ organizationFlags,
+ defaultOrganizationProvider,
+ organizationUpdater,
+ new DefaultGroupFinder(db.getDbClient()),
+ settings.asConfig(),
+ localAuthentication);
+
+ private UserIdentityAuthenticatorImpl underTest = new UserIdentityAuthenticatorImpl(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags,
+ new OrganizationUpdaterImpl(db.getDbClient(), mock(System2.class), UuidFactoryFast.getInstance(),
+ new OrganizationValidationImpl(), settings.asConfig(), null, null, null),
+ new DefaultGroupFinder(db.getDbClient()));
+
+ @Test
+ public void authenticate_new_user() {
+ organizationFlags.setEnabled(true);
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.realm(BASIC, IDENTITY_PROVIDER.getName()))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ UserDto user = db.users().selectUserByLogin(USER_LOGIN).get();
+ assertThat(user).isNotNull();
+ assertThat(user.isActive()).isTrue();
+ assertThat(user.getName()).isEqualTo("John");
+ assertThat(user.getEmail()).isEqualTo("john@email.com");
+ assertThat(user.getExternalLogin()).isEqualTo("johndoo");
+ assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
+ assertThat(user.getExternalId()).isEqualTo("ABCD");
+ assertThat(user.isRoot()).isFalse();
+ checkGroupMembership(user);
+ }
+
+ @Test
+ public void authenticate_new_user_with_groups() {
+ organizationFlags.setEnabled(true);
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
+
+ authenticate(USER_LOGIN, "group1", "group2", "group3");
+
+ Optional<UserDto> user = db.users().selectUserByLogin(USER_LOGIN);
+ checkGroupMembership(user.get(), group1, group2);
+ }
+
+ @Test
+ public void authenticate_new_user_and_force_default_group_when_organizations_are_disabled() {
+ organizationFlags.setEnabled(false);
+ UserDto user = db.users().insertUser();
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto defaultGroup = insertDefaultGroup();
+ db.users().insertMember(group1, user);
+ db.users().insertMember(defaultGroup, user);
+
+ authenticate(user.getLogin(), "group1");
+
+ checkGroupMembership(user, group1, defaultGroup);
+ }
+
+ @Test
+ public void does_not_force_default_group_when_authenticating_new_user_if_organizations_are_enabled() {
+ organizationFlags.setEnabled(true);
+ UserDto user = db.users().insertUser();
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto defaultGroup = insertDefaultGroup();
+ db.users().insertMember(group1, user);
+ db.users().insertMember(defaultGroup, user);
+
+ authenticate(user.getLogin(), "group1");
+
+ checkGroupMembership(user, group1);
+ }
+
+ @Test
+ public void authenticate_new_user_sets_onboarded_flag_to_false_when_onboarding_setting_is_set_to_true() {
+ organizationFlags.setEnabled(true);
+ settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, true);
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isFalse();
+ }
+
+ @Test
+ public void authenticate_new_user_sets_onboarded_flag_to_true_when_onboarding_setting_is_set_to_false() {
+ organizationFlags.setEnabled(true);
+ settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, false);
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isTrue();
+ }
+
+ @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(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(newUser)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ assertThat(db.users().selectUserByLogin(newUser.getLogin()).get())
+ .extracting(UserDto::getLogin, UserDto::getExternalId, UserDto::getExternalLogin)
+ .contains("john", "johndoo", "johndoo");
+ }
+
+ @Test
+ public void authenticate_new_user_update_existing_user_email_when_strategy_is_ALLOW() {
+ organizationFlags.setEnabled(true);
+ UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
+ UserIdentity newUser = UserIdentity.builder()
+ .setProviderLogin("johndoo")
+ .setLogin("new_login")
+ .setName(existingUser.getName())
+ .setEmail(existingUser.getEmail())
+ .build();
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(newUser)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ UserDto newUserReloaded = db.users().selectUserByLogin(newUser.getLogin()).get();
+ assertThat(newUserReloaded.getEmail()).isEqualTo(existingUser.getEmail());
+ UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get();
+ assertThat(existingUserReloaded.getEmail()).isNull();
+ }
+
+ @Test
+ public void throw_EmailAlreadyExistException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_WARN() {
+ organizationFlags.setEnabled(true);
+ UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
+ UserIdentity newUser = UserIdentity.builder()
+ .setProviderLogin("johndoo")
+ .setLogin("new_login")
+ .setName(existingUser.getName())
+ .setEmail(existingUser.getEmail())
+ .build();
+
+ expectedException.expect(EmailAlreadyExistsRedirectionException.class);
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(newUser)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.WARN)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ @Test
+ public void throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() {
+ db.users().insertUser(newUserDto()
+ .setLogin("Existing user with same email")
+ .setActive(true)
+ .setEmail("john@email.com"));
+ Source source = Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName());
+
+ expectedException.expect(authenticationException().from(source)
+ .withLogin(USER_IDENTITY.getLogin())
+ .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
+ "This means that you probably already registered with another account."));
+ expectedException.expectMessage("Email 'john@email.com' is already used");
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(source)
+ .setExistingEmailStrategy(FORBID)
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ @Test
+ public void fail_to_authenticate_new_user_when_allow_users_to_signup_is_false() {
+ TestIdentityProvider identityProvider = new TestIdentityProvider()
+ .setKey("github")
+ .setName("Github")
+ .setEnabled(true)
+ .setAllowsUsersToSignUp(false);
+ Source source = Source.realm(AuthenticationEvent.Method.FORM, identityProvider.getName());
+
+ expectedException.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getLogin()).andPublicMessage("'github' users are not allowed to sign up"));
+ expectedException.expectMessage("User signup disabled for provider 'github'");
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(identityProvider)
+ .setSource(source)
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ @Test
+ public void authenticate_existing_user_matching_login() {
+ db.users().insertUser(u -> u
+ .setLogin(USER_LOGIN)
+ .setName("Old name")
+ .setEmail("Old email")
+ .setExternalId("old id")
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider("old provide"));
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ 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(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ 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(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ 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(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(UserIdentity.builder()
+ .setProviderId(null)
+ .setProviderLogin("johndoo")
+ .setLogin(USER_LOGIN)
+ .setName("John")
+ .setEmail("john@email.com")
+ .build())
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ 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
+ public void authenticate_existing_user_with_login_update_and_strategy_is_ALLOW() {
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin("Old login")
+ .setExternalId(USER_IDENTITY.getProviderId())
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey()));
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(UserDto::getLogin, UserDto::getExternalLogin)
+ .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin());
+ }
+
+ @Test
+ public void authenticate_existing_user_with_login_update_and_personal_org_does_not_exits_and_strategy_is_WARN() {
+ organizationFlags.setEnabled(true);
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin("Old login")
+ .setExternalId(USER_IDENTITY.getProviderId())
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
+ .setOrganizationUuid(null));
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.WARN)
+ .build());
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(UserDto::getLogin, UserDto::getExternalLogin)
+ .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin());
+ }
+
+ @Test
+ public void throw_UpdateLoginRedirectionException_when_authenticating_with_login_update_and_personal_org_exists_and_strategy_is_WARN() {
+ organizationFlags.setEnabled(true);
+ OrganizationDto organization = db.organizations().insert(o -> o.setKey("Old login"));
+ db.users().insertUser(u -> u
+ .setLogin("Old login")
+ .setExternalId(USER_IDENTITY.getProviderId())
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
+ .setOrganizationUuid(organization.getUuid()));
+
+ expectedException.expect(UpdateLoginRedirectionException.class);
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.WARN)
+ .build());
+ }
+
+ @Test
+ public void authenticate_existing_user_and_update_personal_og_key_when_personal_org_exists_and_strategy_is_ALLOW() {
+ organizationFlags.setEnabled(true);
+ OrganizationDto personalOrganization = db.organizations().insert(o -> o.setKey("Old login"));
+ UserDto user = db.users().insertUser(u -> u
+ .setLogin("Old login")
+ .setExternalId(USER_IDENTITY.getProviderId())
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
+ .setOrganizationUuid(personalOrganization.getUuid()));
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ assertThat(db.getDbClient().userDao().selectByUuid(db.getSession(), user.getUuid()))
+ .extracting(UserDto::getLogin, UserDto::getExternalLogin)
+ .contains(USER_LOGIN, USER_IDENTITY.getProviderLogin());
+ OrganizationDto organizationReloaded = db.getDbClient().organizationDao().selectByUuid(db.getSession(), personalOrganization.getUuid()).get();
+ assertThat(organizationReloaded.getKey()).isEqualTo(USER_LOGIN);
+ }
+
+ @Test
+ public void fail_to_authenticate_existing_user_when_personal_org_does_not_exist() {
+ organizationFlags.setEnabled(true);
+ db.users().insertUser(u -> u
+ .setLogin("Old login")
+ .setExternalId(USER_IDENTITY.getProviderId())
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider(IDENTITY_PROVIDER.getKey())
+ .setOrganizationUuid("unknown"));
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Cannot find personal organization uuid 'unknown' for user 'Old login'");
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ @Test
+ public void authenticate_existing_disabled_user() {
+ organizationFlags.setEnabled(true);
+ db.users().insertUser(u -> u
+ .setLogin(USER_LOGIN)
+ .setActive(false)
+ .setName("Old name")
+ .setEmail("Old email")
+ .setExternalId("old id")
+ .setExternalLogin("old identity")
+ .setExternalIdentityProvider("old provide"));
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(USER_IDENTITY)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ 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.getExternalId()).isEqualTo("ABCD");
+ assertThat(userDto.getExternalLogin()).isEqualTo("johndoo");
+ assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github");
+ assertThat(userDto.isRoot()).isFalse();
+ }
+
+ @Test
+ public void authenticate_existing_user_when_email_already_exists_and_strategy_is_ALLOW() {
+ organizationFlags.setEnabled(true);
+ UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
+ UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
+ UserIdentity userIdentity = UserIdentity.builder()
+ .setLogin(currentUser.getLogin())
+ .setProviderLogin("johndoo")
+ .setName("John")
+ .setEmail("john@email.com")
+ .build();
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentity)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.ALLOW)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get();
+ assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com");
+ UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get();
+ assertThat(existingUserReloaded.getEmail()).isNull();
+ }
+
+ @Test
+ public void throw_EmailAlreadyExistException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_WARN() {
+ organizationFlags.setEnabled(true);
+ UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
+ UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
+ UserIdentity userIdentity = UserIdentity.builder()
+ .setLogin(currentUser.getLogin())
+ .setProviderLogin("johndoo")
+ .setName("John")
+ .setEmail("john@email.com")
+ .build();
+
+ expectedException.expect(EmailAlreadyExistsRedirectionException.class);
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentity)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.WARN)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ @Test
+ public void throw_AuthenticationException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_FORBID() {
+ organizationFlags.setEnabled(true);
+ UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
+ UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
+ UserIdentity userIdentity = UserIdentity.builder()
+ .setLogin(currentUser.getLogin())
+ .setProviderLogin("johndoo")
+ .setName("John")
+ .setEmail("john@email.com")
+ .build();
+
+ expectedException.expect(authenticationException().from(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()))
+ .withLogin(userIdentity.getLogin())
+ .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
+ "This means that you probably already registered with another account."));
+ expectedException.expectMessage("Email 'john@email.com' is already used");
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentity)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.realm(AuthenticationEvent.Method.FORM, IDENTITY_PROVIDER.getName()))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ @Test
+ public void does_not_fail_to_authenticate_user_when_email_has_not_changed_and_strategy_is_FORBID() {
+ organizationFlags.setEnabled(true);
+ UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
+ UserIdentity userIdentity = UserIdentity.builder()
+ .setLogin(currentUser.getLogin())
+ .setProviderLogin("johndoo")
+ .setName("John")
+ .setEmail("john@email.com")
+ .build();
+
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(userIdentity)
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get();
+ assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com");
+ }
+
+ @Test
+ public void authenticate_existing_user_and_add_new_groups() {
+ organizationFlags.setEnabled(true);
+ UserDto user = db.users().insertUser(newUserDto()
+ .setLogin(USER_LOGIN)
+ .setActive(true)
+ .setName("John"));
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
+
+ authenticate(USER_LOGIN, "group1", "group2", "group3");
+
+ checkGroupMembership(user, group1, group2);
+ }
+
+ @Test
+ public void authenticate_existing_user_and_remove_groups() {
+ organizationFlags.setEnabled(true);
+ UserDto user = db.users().insertUser(newUserDto()
+ .setLogin(USER_LOGIN)
+ .setActive(true)
+ .setName("John"));
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
+ db.users().insertMember(group1, user);
+ db.users().insertMember(group2, user);
+
+ authenticate(USER_LOGIN, "group1");
+
+ checkGroupMembership(user, group1);
+ }
+
+ @Test
+ public void authenticate_existing_user_and_remove_all_groups_expect_default_when_organizations_are_disabled() {
+ organizationFlags.setEnabled(false);
+ UserDto user = db.users().insertUser();
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
+ GroupDto defaultGroup = insertDefaultGroup();
+ db.users().insertMember(group1, user);
+ db.users().insertMember(group2, user);
+ db.users().insertMember(defaultGroup, user);
+
+ authenticate(user.getLogin());
+
+ checkGroupMembership(user, defaultGroup);
+ }
+
+ @Test
+ public void does_not_force_default_group_when_authenticating_existing_user_when_organizations_are_enabled() {
+ organizationFlags.setEnabled(true);
+ UserDto user = db.users().insertUser();
+ GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
+ GroupDto defaultGroup = insertDefaultGroup();
+ db.users().insertMember(group1, user);
+ db.users().insertMember(defaultGroup, user);
+
+ authenticate(user.getLogin(), "group1");
+
+ checkGroupMembership(user, group1);
+ }
+
+ @Test
+ public void ignore_groups_on_non_default_organizations() {
+ organizationFlags.setEnabled(true);
+ OrganizationDto org = db.organizations().insert();
+ UserDto user = db.users().insertUser(newUserDto()
+ .setLogin(USER_LOGIN)
+ .setActive(true)
+ .setName("John"));
+ String groupName = "a-group";
+ GroupDto groupInDefaultOrg = db.users().insertGroup(db.getDefaultOrganization(), groupName);
+ GroupDto groupInOrg = db.users().insertGroup(org, groupName);
+
+ // adding a group with the same name than in non-default organization
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(UserIdentity.builder()
+ .setProviderLogin("johndoo")
+ .setLogin(user.getLogin())
+ .setName(user.getName())
+ .setGroups(newHashSet(groupName))
+ .build())
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+
+ checkGroupMembership(user, groupInDefaultOrg);
+ }
+
+ private void authenticate(String login, String... groups) {
+ underTest.authenticate(UserIdentityAuthenticatorParameters.builder()
+ .setUserIdentity(UserIdentity.builder()
+ .setProviderLogin("johndoo")
+ .setLogin(login)
+ .setName("John")
+ // No group
+ .setGroups(stream(groups).collect(MoreCollectors.toSet()))
+ .build())
+ .setProvider(IDENTITY_PROVIDER)
+ .setSource(Source.local(BASIC))
+ .setExistingEmailStrategy(ExistingEmailStrategy.FORBID)
+ .setUpdateLoginStrategy(UpdateLoginStrategy.ALLOW)
+ .build());
+ }
+
+ private void checkGroupMembership(UserDto user, GroupDto... expectedGroups) {
+ assertThat(db.users().selectGroupIdsOfUser(user)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(Collectors.toList()).toArray(new Integer[] {}));
+ }
+
+ private GroupDto insertDefaultGroup() {
+ return db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users");
+ }
+
+}
+++ /dev/null
-/*
- * 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.authentication;
-
-import java.util.Optional;
-import java.util.stream.Collectors;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.server.authentication.UserIdentity;
-import org.sonar.api.utils.internal.AlwaysIncreasingSystem2;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbTester;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.authentication.event.AuthenticationEvent.Method;
-import org.sonar.server.authentication.event.AuthenticationEvent.Source;
-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.NewUserNotifier;
-import org.sonar.server.user.UserUpdater;
-import org.sonar.server.user.index.UserIndexer;
-import org.sonar.server.usergroups.DefaultGroupFinder;
-
-import static com.google.common.collect.Sets.newHashSet;
-import static java.util.Arrays.stream;
-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;
-import static org.sonar.db.user.UserTesting.newUserDto;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.ALLOW;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.FORBID;
-import static org.sonar.server.authentication.UserIdentityAuthenticator.ExistingEmailStrategy.WARN;
-import static org.sonar.server.authentication.event.AuthenticationExceptionMatcher.authenticationException;
-
-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")
- .setEmail("john@email.com")
- .build();
-
- private static TestIdentityProvider IDENTITY_PROVIDER = new TestIdentityProvider()
- .setKey("github")
- .setName("name of github")
- .setEnabled(true)
- .setAllowsUsersToSignUp(true);
-
- private MapSettings settings = new MapSettings();
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
- @Rule
- public DbTester db = DbTester.create(new AlwaysIncreasingSystem2());
- @Rule
- public EsTester es = EsTester.create();
- private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
- private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
- private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
- private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
- private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
- private UserUpdater userUpdater = new UserUpdater(
- mock(NewUserNotifier.class),
- db.getDbClient(),
- userIndexer,
- organizationFlags,
- defaultOrganizationProvider,
- organizationCreation,
- new DefaultGroupFinder(db.getDbClient()),
- settings.asConfig(),
- localAuthentication);
-
- private UserIdentityAuthenticator underTest = new UserIdentityAuthenticator(db.getDbClient(), userUpdater, defaultOrganizationProvider, organizationFlags,
- new DefaultGroupFinder(db.getDbClient()));
-
- @Test
- public void authenticate_new_user() {
- organizationFlags.setEnabled(true);
-
- underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.realm(Method.BASIC, IDENTITY_PROVIDER.getName()), FORBID);
-
- UserDto user = db.users().selectUserByLogin(USER_LOGIN).get();
- assertThat(user).isNotNull();
- assertThat(user.isActive()).isTrue();
- assertThat(user.getName()).isEqualTo("John");
- assertThat(user.getEmail()).isEqualTo("john@email.com");
- assertThat(user.getExternalLogin()).isEqualTo("johndoo");
- assertThat(user.getExternalIdentityProvider()).isEqualTo("github");
- assertThat(user.getExternalId()).isEqualTo("ABCD");
- assertThat(user.isRoot()).isFalse();
- checkGroupMembership(user);
- }
-
- @Test
- public void authenticate_new_user_with_groups() {
- organizationFlags.setEnabled(true);
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
-
- authenticate(USER_LOGIN, "group1", "group2", "group3");
-
- Optional<UserDto> user = db.users().selectUserByLogin(USER_LOGIN);
- checkGroupMembership(user.get(), group1, group2);
- }
-
- @Test
- public void authenticate_new_user_and_force_default_group_when_organizations_are_disabled() {
- organizationFlags.setEnabled(false);
- UserDto user = db.users().insertUser();
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto defaultGroup = insertDefaultGroup();
- db.users().insertMember(group1, user);
- db.users().insertMember(defaultGroup, user);
-
- authenticate(user.getLogin(), "group1");
-
- checkGroupMembership(user, group1, defaultGroup);
- }
-
- @Test
- public void does_not_force_default_group_when_authenticating_new_user_if_organizations_are_enabled() {
- organizationFlags.setEnabled(true);
- UserDto user = db.users().insertUser();
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto defaultGroup = insertDefaultGroup();
- db.users().insertMember(group1, user);
- db.users().insertMember(defaultGroup, user);
-
- authenticate(user.getLogin(), "group1");
-
- checkGroupMembership(user, group1);
- }
-
- @Test
- public void authenticate_new_user_sets_onboarded_flag_to_false_when_onboarding_setting_is_set_to_true() {
- organizationFlags.setEnabled(true);
- settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, true);
-
- underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.realm(Method.BASIC, IDENTITY_PROVIDER.getName()), FORBID);
-
- assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isFalse();
- }
-
- @Test
- public void authenticate_new_user_sets_onboarded_flag_to_true_when_onboarding_setting_is_set_to_false() {
- organizationFlags.setEnabled(true);
- settings.setProperty(ONBOARDING_TUTORIAL_SHOW_TO_NEW_USERS, false);
-
- underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.realm(Method.BASIC, IDENTITY_PROVIDER.getName()), FORBID);
-
- assertThat(db.users().selectUserByLogin(USER_LOGIN).get().isOnboarded()).isTrue();
- }
-
- @Test
- public void authenticate_new_user_update_existing_user_email_when_strategy_is_ALLOW() {
- organizationFlags.setEnabled(true);
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserIdentity newUser = UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setLogin("new_login")
- .setName(existingUser.getName())
- .setEmail(existingUser.getEmail())
- .build();
-
- underTest.authenticate(newUser, IDENTITY_PROVIDER, Source.local(Method.BASIC), ALLOW);
-
- UserDto newUserReloaded = db.users().selectUserByLogin(newUser.getLogin()).get();
- assertThat(newUserReloaded.getEmail()).isEqualTo(existingUser.getEmail());
- UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get();
- 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);
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserIdentity newUser = UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setLogin("new_login")
- .setName(existingUser.getName())
- .setEmail(existingUser.getEmail())
- .build();
-
- expectedException.expect(EmailAlreadyExistsException.class);
-
- underTest.authenticate(newUser, IDENTITY_PROVIDER, Source.local(Method.BASIC), WARN);
- }
-
- @Test
- public void throw_AuthenticationException_when_authenticating_new_user_when_email_already_exists_and_strategy_is_FORBID() {
- db.users().insertUser(newUserDto()
- .setLogin("Existing user with same email")
- .setActive(true)
- .setEmail("john@email.com"));
- Source source = Source.realm(Method.FORM, IDENTITY_PROVIDER.getName());
-
- expectedException.expect(authenticationException().from(source)
- .withLogin(USER_IDENTITY.getLogin())
- .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
- "This means that you probably already registered with another account."));
- expectedException.expectMessage("Email 'john@email.com' is already used");
-
- underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, source, FORBID);
- }
-
- @Test
- public void fail_to_authenticate_new_user_when_allow_users_to_signup_is_false() {
- TestIdentityProvider identityProvider = new TestIdentityProvider()
- .setKey("github")
- .setName("Github")
- .setEnabled(true)
- .setAllowsUsersToSignUp(false);
- Source source = Source.realm(Method.FORM, identityProvider.getName());
-
- expectedException.expect(authenticationException().from(source).withLogin(USER_IDENTITY.getLogin()).andPublicMessage("'github' users are not allowed to sign up"));
- expectedException.expectMessage("User signup disabled for provider 'github'");
- underTest.authenticate(USER_IDENTITY, identityProvider, source, FORBID);
- }
-
- @Test
- public void authenticate_existing_user_matching_login() {
- db.users().insertUser(u -> u
- .setLogin(USER_LOGIN)
- .setName("Old name")
- .setEmail("Old email")
- .setExternalId("old id")
- .setExternalLogin("old identity")
- .setExternalIdentityProvider("old provide"));
-
- underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.local(Method.BASIC), FORBID);
-
- 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
- public void authenticate_existing_disabled_user() {
- organizationFlags.setEnabled(true);
- db.users().insertUser(u -> u
- .setLogin(USER_LOGIN)
- .setActive(false)
- .setName("Old name")
- .setEmail("Old email")
- .setExternalId("old id")
- .setExternalLogin("old identity")
- .setExternalIdentityProvider("old provide"));
-
- underTest.authenticate(USER_IDENTITY, IDENTITY_PROVIDER, Source.local(Method.BASIC_TOKEN), 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.getExternalId()).isEqualTo("ABCD");
- assertThat(userDto.getExternalLogin()).isEqualTo("johndoo");
- assertThat(userDto.getExternalIdentityProvider()).isEqualTo("github");
- assertThat(userDto.isRoot()).isFalse();
- }
-
- @Test
- public void authenticate_existing_user_when_email_already_exists_and_strategy_is_ALLOW() {
- organizationFlags.setEnabled(true);
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
- UserIdentity userIdentity = UserIdentity.builder()
- .setLogin(currentUser.getLogin())
- .setProviderLogin("johndoo")
- .setName("John")
- .setEmail("john@email.com")
- .build();
-
- underTest.authenticate(userIdentity, IDENTITY_PROVIDER, Source.local(Method.BASIC), ALLOW);
-
- UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get();
- assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com");
- UserDto existingUserReloaded = db.users().selectUserByLogin(existingUser.getLogin()).get();
- assertThat(existingUserReloaded.getEmail()).isNull();
- }
-
- @Test
- public void throw_EmailAlreadyExistException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_WARN() {
- organizationFlags.setEnabled(true);
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
- UserIdentity userIdentity = UserIdentity.builder()
- .setLogin(currentUser.getLogin())
- .setProviderLogin("johndoo")
- .setName("John")
- .setEmail("john@email.com")
- .build();
-
- expectedException.expect(EmailAlreadyExistsException.class);
-
- underTest.authenticate(userIdentity, IDENTITY_PROVIDER, Source.local(Method.BASIC), WARN);
- }
-
- @Test
- public void throw_AuthenticationException_when_authenticating_existing_user_when_email_already_exists_and_strategy_is_FORBID() {
- organizationFlags.setEnabled(true);
- UserDto existingUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserDto currentUser = db.users().insertUser(u -> u.setEmail(null));
- UserIdentity userIdentity = UserIdentity.builder()
- .setLogin(currentUser.getLogin())
- .setProviderLogin("johndoo")
- .setName("John")
- .setEmail("john@email.com")
- .build();
-
- expectedException.expect(authenticationException().from(Source.realm(Method.FORM, IDENTITY_PROVIDER.getName()))
- .withLogin(userIdentity.getLogin())
- .andPublicMessage("You can't sign up because email 'john@email.com' is already used by an existing user. " +
- "This means that you probably already registered with another account."));
- expectedException.expectMessage("Email 'john@email.com' is already used");
-
- underTest.authenticate(userIdentity, IDENTITY_PROVIDER, Source.realm(Method.FORM, IDENTITY_PROVIDER.getName()), FORBID);
- }
-
- @Test
- public void does_not_fail_to_authenticate_user_when_email_has_not_changed_and_strategy_is_FORBID() {
- organizationFlags.setEnabled(true);
- UserDto currentUser = db.users().insertUser(u -> u.setEmail("john@email.com"));
- UserIdentity userIdentity = UserIdentity.builder()
- .setLogin(currentUser.getLogin())
- .setProviderLogin("johndoo")
- .setName("John")
- .setEmail("john@email.com")
- .build();
-
- underTest.authenticate(userIdentity, IDENTITY_PROVIDER, Source.local(Method.BASIC), FORBID);
-
- UserDto currentUserReloaded = db.users().selectUserByLogin(currentUser.getLogin()).get();
- assertThat(currentUserReloaded.getEmail()).isEqualTo("john@email.com");
- }
-
- @Test
- public void authenticate_existing_user_and_add_new_groups() {
- organizationFlags.setEnabled(true);
- UserDto user = db.users().insertUser(newUserDto()
- .setLogin(USER_LOGIN)
- .setActive(true)
- .setName("John"));
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
-
- authenticate(USER_LOGIN, "group1", "group2", "group3");
-
- checkGroupMembership(user, group1, group2);
- }
-
- @Test
- public void authenticate_existing_user_and_remove_groups() {
- organizationFlags.setEnabled(true);
- UserDto user = db.users().insertUser(newUserDto()
- .setLogin(USER_LOGIN)
- .setActive(true)
- .setName("John"));
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
- db.users().insertMember(group1, user);
- db.users().insertMember(group2, user);
-
- authenticate(USER_LOGIN, "group1");
-
- checkGroupMembership(user, group1);
- }
-
- @Test
- public void authenticate_existing_user_and_remove_all_groups_expect_default_when_organizations_are_disabled() {
- organizationFlags.setEnabled(false);
- UserDto user = db.users().insertUser();
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto group2 = db.users().insertGroup(db.getDefaultOrganization(), "group2");
- GroupDto defaultGroup = insertDefaultGroup();
- db.users().insertMember(group1, user);
- db.users().insertMember(group2, user);
- db.users().insertMember(defaultGroup, user);
-
- authenticate(user.getLogin());
-
- checkGroupMembership(user, defaultGroup);
- }
-
- @Test
- public void does_not_force_default_group_when_authenticating_existing_user_when_organizations_are_enabled() {
- organizationFlags.setEnabled(true);
- UserDto user = db.users().insertUser();
- GroupDto group1 = db.users().insertGroup(db.getDefaultOrganization(), "group1");
- GroupDto defaultGroup = insertDefaultGroup();
- db.users().insertMember(group1, user);
- db.users().insertMember(defaultGroup, user);
-
- authenticate(user.getLogin(), "group1");
-
- checkGroupMembership(user, group1);
- }
-
- @Test
- public void ignore_groups_on_non_default_organizations() {
- organizationFlags.setEnabled(true);
- OrganizationDto org = db.organizations().insert();
- UserDto user = db.users().insertUser(newUserDto()
- .setLogin(USER_LOGIN)
- .setActive(true)
- .setName("John"));
- String groupName = "a-group";
- GroupDto groupInDefaultOrg = db.users().insertGroup(db.getDefaultOrganization(), groupName);
- GroupDto groupInOrg = db.users().insertGroup(org, groupName);
-
- // adding a group with the same name than in non-default organization
- underTest.authenticate(UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setLogin(user.getLogin())
- .setName(user.getName())
- .setGroups(newHashSet(groupName))
- .build(), IDENTITY_PROVIDER, Source.sso(), FORBID);
-
- checkGroupMembership(user, groupInDefaultOrg);
- }
-
- private void authenticate(String login, String... groups) {
- underTest.authenticate(UserIdentity.builder()
- .setProviderLogin("johndoo")
- .setLogin(login)
- .setName("John")
- // No group
- .setGroups(stream(groups).collect(MoreCollectors.toSet()))
- .build(), IDENTITY_PROVIDER, Source.sso(), FORBID);
- }
-
- private void checkGroupMembership(UserDto user, GroupDto... expectedGroups) {
- assertThat(db.users().selectGroupIdsOfUser(user)).containsOnly(stream(expectedGroups).map(GroupDto::getId).collect(Collectors.toList()).toArray(new Integer[] {}));
- }
-
- private GroupDto insertDefaultGroup() {
- return db.users().insertDefaultGroup(db.getDefaultOrganization(), "sonar-users");
- }
-}
+++ /dev/null
-/*
- * 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.organization;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import org.apache.commons.lang.RandomStringUtils;
-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.TestSystem2;
-import org.sonar.api.web.UserRole;
-import org.sonar.core.config.CorePropertyDefinitions;
-import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.organization.DefaultTemplates;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto;
-import org.sonar.db.permission.template.PermissionTemplateDto;
-import org.sonar.db.permission.template.PermissionTemplateGroupDto;
-import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.RulesProfileDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserMembershipDto;
-import org.sonar.db.user.UserMembershipQuery;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.qualitygate.QualityGateFinder;
-import org.sonar.server.qualityprofile.BuiltInQProfile;
-import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryRule;
-import org.sonar.server.qualityprofile.QProfileName;
-import org.sonar.server.user.index.UserIndex;
-import org.sonar.server.user.index.UserIndexer;
-import org.sonar.server.user.index.UserQuery;
-import org.sonar.server.usergroups.DefaultGroupCreator;
-import org.sonar.server.usergroups.DefaultGroupCreatorImpl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.fail;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.server.language.LanguageTesting.newLanguage;
-import static org.sonar.server.organization.OrganizationCreation.NewOrganization.newOrganizationBuilder;
-
-public class OrganizationCreationImplTest {
- private static final long A_DATE = 12893434L;
- private static final String A_LOGIN = "a-login";
- private static final String SLUG_OF_A_LOGIN = "slug-of-a-login";
- private static final String STRING_64_CHARS = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
- private static final String A_NAME = "a name";
-
- private OrganizationCreation.NewOrganization FULL_POPULATED_NEW_ORGANIZATION = newOrganizationBuilder()
- .setName("a-name")
- .setKey("a-key")
- .setDescription("a-description")
- .setUrl("a-url")
- .setAvatarUrl("a-avatar")
- .build();
-
- private System2 system2 = new TestSystem2().setNow(A_DATE);
-
- @Rule
- public DbTester db = DbTester.create(system2);
- @Rule
- public EsTester es = EsTester.create();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
- @Rule
- public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule();
-
- private DbSession dbSession = db.getSession();
-
- private IllegalArgumentException exceptionThrownByOrganizationValidation = new IllegalArgumentException("simulate IAE thrown by OrganizationValidation");
- private DbClient dbClient = db.getDbClient();
- private UuidFactory uuidFactory = new SequenceUuidFactory();
- private OrganizationValidation organizationValidation = mock(OrganizationValidation.class);
- private MapSettings settings = new MapSettings();
- private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
- private UserIndex userIndex = new UserIndex(es.client(), system2);
- private DefaultGroupCreator defaultGroupCreator = new DefaultGroupCreatorImpl(dbClient);
- private QualityGateFinder qualityGateFinder = new QualityGateFinder(dbClient);
- private OrganizationCreationImpl underTest = new OrganizationCreationImpl(dbClient, system2, uuidFactory, organizationValidation, settings.asConfig(), userIndexer,
- builtInQProfileRepositoryRule, defaultGroupCreator);
-
- @Test
- public void create_throws_NPE_if_NewOrganization_arg_is_null() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
-
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("newOrganization can't be null");
-
- underTest.create(dbSession, user, null);
- }
-
- @Test
- public void create_throws_exception_thrown_by_checkValidKey() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
-
- when(organizationValidation.checkKey(FULL_POPULATED_NEW_ORGANIZATION.getKey()))
- .thenThrow(exceptionThrownByOrganizationValidation);
-
- createThrowsExceptionThrownByOrganizationValidation(user);
- }
-
- @Test
- public void create_throws_exception_thrown_by_checkValidDescription() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
-
- when(organizationValidation.checkDescription(FULL_POPULATED_NEW_ORGANIZATION.getDescription())).thenThrow(exceptionThrownByOrganizationValidation);
-
- createThrowsExceptionThrownByOrganizationValidation(user);
- }
-
- @Test
- public void create_throws_exception_thrown_by_checkValidUrl() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
-
- when(organizationValidation.checkUrl(FULL_POPULATED_NEW_ORGANIZATION.getUrl())).thenThrow(exceptionThrownByOrganizationValidation);
-
- createThrowsExceptionThrownByOrganizationValidation(user);
- }
-
- @Test
- public void create_throws_exception_thrown_by_checkValidAvatar() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
-
- when(organizationValidation.checkAvatar(FULL_POPULATED_NEW_ORGANIZATION.getAvatar())).thenThrow(exceptionThrownByOrganizationValidation);
-
- createThrowsExceptionThrownByOrganizationValidation(user);
- }
-
- private void createThrowsExceptionThrownByOrganizationValidation(UserDto user) throws OrganizationCreation.KeyConflictException {
- try {
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
- fail(exceptionThrownByOrganizationValidation + " should have been thrown");
- } catch (IllegalArgumentException e) {
- assertThat(e).isSameAs(exceptionThrownByOrganizationValidation);
- }
- }
-
- @Test
- public void create_fails_with_ISE_if_BuiltInQProfileRepository_has_not_been_initialized() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
- db.qualityGates().insertBuiltInQualityGate();
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("initialize must be called first");
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
- }
-
- @Test
- public void create_fails_with_KeyConflictException_if_org_with_key_in_NewOrganization_arg_already_exists_in_db() throws OrganizationCreation.KeyConflictException {
- db.organizations().insertForKey(FULL_POPULATED_NEW_ORGANIZATION.getKey());
- UserDto user = db.users().insertUser();
-
- expectedException.expect(OrganizationCreation.KeyConflictException.class);
- expectedException.expectMessage("Organization key '" + FULL_POPULATED_NEW_ORGANIZATION.getKey() + "' is already used");
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
- }
-
- @Test
- public void create_creates_unguarded_organization_with_properties_from_NewOrganization_arg() throws OrganizationCreation.KeyConflictException {
- builtInQProfileRepositoryRule.initialize();
- UserDto user = db.users().insertUser();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
- assertThat(organization.getUuid()).isNotEmpty();
- assertThat(organization.getKey()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getKey());
- assertThat(organization.getName()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getName());
- assertThat(organization.getDescription()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getDescription());
- assertThat(organization.getUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getUrl());
- assertThat(organization.getAvatarUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getAvatar());
- assertThat(organization.isGuarded()).isFalse();
- assertThat(organization.getUserId()).isNull();
- assertThat(organization.getCreatedAt()).isEqualTo(A_DATE);
- assertThat(organization.getUpdatedAt()).isEqualTo(A_DATE);
- }
-
- @Test
- public void create_creates_owners_group_with_all_permissions_for_new_organization_and_add_current_user_to_it() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- verifyGroupOwners(user, FULL_POPULATED_NEW_ORGANIZATION.getKey(), FULL_POPULATED_NEW_ORGANIZATION.getName());
- }
-
- @Test
- public void create_creates_members_group_and_add_current_user_to_it() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- verifyMembersGroup(user, FULL_POPULATED_NEW_ORGANIZATION.getKey());
- }
-
- @Test
- public void create_does_not_require_description_url_and_avatar_to_be_non_null() throws OrganizationCreation.KeyConflictException {
- builtInQProfileRepositoryRule.initialize();
- UserDto user = db.users().insertUser();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.create(dbSession, user, newOrganizationBuilder()
- .setKey("key")
- .setName("name")
- .build());
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, "key").get();
- assertThat(organization.getKey()).isEqualTo("key");
- assertThat(organization.getName()).isEqualTo("name");
- assertThat(organization.getDescription()).isNull();
- assertThat(organization.getUrl()).isNull();
- assertThat(organization.getAvatarUrl()).isNull();
- assertThat(organization.isGuarded()).isFalse();
- assertThat(organization.getUserId()).isNull();
- }
-
- @Test
- public void create_creates_default_template_for_new_organization() throws OrganizationCreation.KeyConflictException {
- builtInQProfileRepositoryRule.initialize();
- UserDto user = db.users().insertUser();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
- GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get();
- int defaultGroupId = dbClient.organizationDao().getDefaultGroupId(dbSession, organization.getUuid()).get();
- PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template");
- assertThat(defaultTemplate.getName()).isEqualTo("Default template");
- assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + FULL_POPULATED_NEW_ORGANIZATION.getName());
- DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get();
- assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid());
- assertThat(defaultTemplates.getViewUuid()).isNull();
- assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
- .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
- .containsOnly(
- tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION),
- tuple(defaultGroupId, UserRole.USER), tuple(defaultGroupId, UserRole.CODEVIEWER));
- }
-
- @Test
- public void create_add_current_user_as_member_of_organization() throws OrganizationCreation.KeyConflictException {
- UserDto user = db.users().insertUser();
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- OrganizationDto result = underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- assertThat(dbClient.organizationMemberDao().select(dbSession, result.getUuid(), user.getId())).isPresent();
- assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(result.getUuid()).setTextQuery(user.getLogin()).build(), new SearchOptions()).getTotal()).isEqualTo(1L);
- }
-
- @Test
- public void create_associates_to_built_in_quality_profiles() throws OrganizationCreation.KeyConflictException {
- BuiltInQProfile builtIn1 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp1", true);
- BuiltInQProfile builtIn2 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp2");
- builtInQProfileRepositoryRule.initialize();
- insertRulesProfile(builtIn1);
- insertRulesProfile(builtIn2);
- UserDto user = db.users().insertUser();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
- List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
- assertThat(profiles).extracting(p -> new QProfileName(p.getLanguage(), p.getName())).containsExactlyInAnyOrder(
- builtIn1.getQProfileName(), builtIn2.getQProfileName());
- assertThat(dbClient.qualityProfileDao().selectDefaultProfile(dbSession, organization, "foo").getName())
- .isEqualTo("qp1");
- }
-
- private void insertRulesProfile(BuiltInQProfile builtIn) {
- RulesProfileDto dto = new RulesProfileDto()
- .setIsBuiltIn(true)
- .setKee(RandomStringUtils.randomAlphabetic(40))
- .setLanguage(builtIn.getLanguage())
- .setName(builtIn.getName());
- dbClient.qualityProfileDao().insert(db.getSession(), dto);
- db.commit();
- }
-
- @Test
- public void create_associates_to_built_in_quality_gate() throws OrganizationCreation.KeyConflictException {
- QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
- builtInQProfileRepositoryRule.initialize();
- UserDto user = db.users().insertUser();
-
- underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
- assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(builtInQualityGate.getUuid());
- }
-
- @Test
- public void createForUser_has_no_effect_if_setting_for_feature_is_not_set() {
- checkSizeOfTables();
-
- underTest.createForUser(null /* argument is not even read */, null /* argument is not even read */);
-
- checkSizeOfTables();
- }
-
- @Test
- public void createForUser_has_no_effect_if_setting_for_feature_is_disabled() {
- enableCreatePersonalOrg(false);
-
- checkSizeOfTables();
-
- underTest.createForUser(null /* argument is not even read */, null /* argument is not even read */);
-
- checkSizeOfTables();
- }
-
- private void checkSizeOfTables() {
- assertThat(db.countRowsOfTable("organizations")).isEqualTo(1);
- assertThat(db.countRowsOfTable("groups")).isEqualTo(0);
- assertThat(db.countRowsOfTable("groups_users")).isEqualTo(0);
- assertThat(db.countRowsOfTable("permission_templates")).isEqualTo(0);
- assertThat(db.countRowsOfTable("perm_templates_users")).isEqualTo(0);
- assertThat(db.countRowsOfTable("perm_templates_groups")).isEqualTo(0);
- }
-
- @Test
- public void createForUser_creates_guarded_organization_with_key_name_and_description_generated_from_user_login_and_name_and_associated_to_user() {
- UserDto user = db.users().insertUser(A_LOGIN);
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(organization.getUuid()).isNotEmpty();
- assertThat(organization.getKey()).isEqualTo(SLUG_OF_A_LOGIN);
- assertThat(organization.getName()).isEqualTo(user.getName());
- assertThat(organization.getDescription()).isEqualTo(user.getName() + "'s personal organization");
- assertThat(organization.getUrl()).isNull();
- assertThat(organization.getAvatarUrl()).isNull();
- assertThat(organization.isGuarded()).isTrue();
- assertThat(organization.getUserId()).isEqualTo(user.getId());
- assertThat(organization.getCreatedAt()).isEqualTo(A_DATE);
- assertThat(organization.getUpdatedAt()).isEqualTo(A_DATE);
- }
-
- @Test
- public void createForUser_fails_with_ISE_if_organization_with_slug_of_login_already_exists() {
- UserDto user = db.users().insertUser(A_LOGIN);
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- db.organizations().insertForKey(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Can't create organization with key '" + SLUG_OF_A_LOGIN + "' for new user '" + A_LOGIN
- + "' because an organization with this key already exists");
-
- underTest.createForUser(dbSession, user);
- }
-
- @Test
- public void createForUser_gives_all_permissions_for_new_organization_to_current_user() {
- UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, user.getId(), organization.getUuid()))
- .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()]));
- }
-
- @Test
- public void createForUser_creates_members_group_and_add_current_user_to_it() {
- UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- verifyMembersGroup(user, SLUG_OF_A_LOGIN);
- }
-
- @Test
- public void createForUser_creates_default_template_for_new_organization() {
- UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- int defaultGroupId = dbClient.organizationDao().getDefaultGroupId(dbSession, organization.getUuid()).get();
- PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template");
- assertThat(defaultTemplate.getName()).isEqualTo("Default template");
- assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + A_NAME);
- DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get();
- assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid());
- assertThat(defaultTemplates.getViewUuid()).isNull();
- assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
- .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
- .containsOnly(
- tuple(defaultGroupId, UserRole.USER), tuple(defaultGroupId, UserRole.CODEVIEWER));
- assertThat(dbClient.permissionTemplateCharacteristicDao().selectByTemplateIds(dbSession, Collections.singletonList(defaultTemplate.getId())))
- .extracting(PermissionTemplateCharacteristicDto::getWithProjectCreator, PermissionTemplateCharacteristicDto::getPermission)
- .containsOnly(
- tuple(true, UserRole.ADMIN), tuple(true, UserRole.ISSUE_ADMIN), tuple(true, GlobalPermissions.SCAN_EXECUTION));
- }
-
- @Test
- public void createForUser_add_current_user_as_member_of_organization() {
- UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), user.getId())).isPresent();
- }
-
- @Test
- public void createForUser_does_not_fail_if_name_is_too_long_for_an_organization_name() {
- String nameTooLong = STRING_64_CHARS + "b";
- UserDto user = db.users().insertUser(dto -> dto.setName(nameTooLong).setLogin(A_LOGIN));
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(organization.getName()).isEqualTo(STRING_64_CHARS);
- assertThat(organization.getDescription()).isEqualTo(nameTooLong + "'s personal organization");
- }
-
- @Test
- public void createForUser_does_not_fail_if_name_is_empty_and_login_is_too_long_for_an_organization_name() {
- String login = STRING_64_CHARS + "b";
- UserDto user = db.users().insertUser(dto -> dto.setName("").setLogin(login));
- when(organizationValidation.generateKeyFrom(login)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(organization.getName()).isEqualTo(STRING_64_CHARS);
- assertThat(organization.getDescription()).isEqualTo(login + "'s personal organization");
- }
-
- @Test
- public void createForUser_does_not_fail_if_name_is_null_and_login_is_too_long_for_an_organization_name() {
- String login = STRING_64_CHARS + "b";
- UserDto user = db.users().insertUser(dto -> dto.setName(null).setLogin(login));
- when(organizationValidation.generateKeyFrom(login)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
- db.qualityGates().insertBuiltInQualityGate();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(organization.getName()).isEqualTo(STRING_64_CHARS);
- assertThat(organization.getDescription()).isEqualTo(login + "'s personal organization");
- }
-
- @Test
- public void createForUser_associates_to_built_in_quality_profiles() {
- UserDto user = db.users().insertUser(A_LOGIN);
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- db.qualityGates().insertBuiltInQualityGate();
- BuiltInQProfile builtIn1 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp1");
- BuiltInQProfile builtIn2 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp2");
- builtInQProfileRepositoryRule.initialize();
- insertRulesProfile(builtIn1);
- insertRulesProfile(builtIn2);
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
- assertThat(profiles).extracting(p -> new QProfileName(p.getLanguage(), p.getName())).containsExactlyInAnyOrder(
- builtIn1.getQProfileName(), builtIn2.getQProfileName());
- }
-
- @Test
- public void createForUser_associates_to_built_in_quality_gate() {
- QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
- UserDto user = db.users().insertUser(A_LOGIN);
- when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
- enableCreatePersonalOrg(true);
- builtInQProfileRepositoryRule.initialize();
-
- underTest.createForUser(dbSession, user);
-
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
- assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(builtInQualityGate.getUuid());
- }
-
- private void enableCreatePersonalOrg(boolean flag) {
- settings.setProperty(CorePropertyDefinitions.ORGANIZATIONS_CREATE_PERSONAL_ORG, flag);
- }
-
- private void verifyGroupOwners(UserDto user, String organizationKey, String organizationName) {
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get();
- Optional<GroupDto> groupOpt = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners");
- assertThat(groupOpt).isPresent();
- GroupDto groupDto = groupOpt.get();
- assertThat(groupDto.getDescription()).isEqualTo("Owners of organization " + organizationName);
-
- assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId()))
- .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()]));
- List<UserMembershipDto> members = dbClient.groupMembershipDao().selectMembers(
- dbSession,
- UserMembershipQuery.builder()
- .organizationUuid(organization.getUuid())
- .groupId(groupDto.getId())
- .membership(UserMembershipQuery.IN).build(),
- 0, Integer.MAX_VALUE);
- assertThat(members)
- .extracting(UserMembershipDto::getLogin)
- .containsOnly(user.getLogin());
- }
-
- private void verifyMembersGroup(UserDto user, String organizationKey) {
- OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get();
- Optional<GroupDto> groupOpt = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Members");
- assertThat(groupOpt).isPresent();
- GroupDto groupDto = groupOpt.get();
- assertThat(groupDto.getDescription()).isEqualTo("All members of the organization");
-
- assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId())).isEmpty();
- List<UserMembershipDto> members = dbClient.groupMembershipDao().selectMembers(
- dbSession,
- UserMembershipQuery.builder()
- .organizationUuid(organization.getUuid())
- .groupId(groupDto.getId())
- .membership(UserMembershipQuery.IN).build(),
- 0, Integer.MAX_VALUE);
- assertThat(members)
- .extracting(UserMembershipDto::getLogin)
- .containsOnly(user.getLogin());
- }
-
-}
--- /dev/null
+/*
+ * 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.organization;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang.RandomStringUtils;
+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.TestSystem2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.config.CorePropertyDefinitions;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.organization.DefaultTemplates;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto;
+import org.sonar.db.permission.template.PermissionTemplateDto;
+import org.sonar.db.permission.template.PermissionTemplateGroupDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.RulesProfileDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserMembershipDto;
+import org.sonar.db.user.UserMembershipQuery;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.SearchOptions;
+import org.sonar.server.qualityprofile.BuiltInQProfile;
+import org.sonar.server.qualityprofile.BuiltInQProfileRepositoryRule;
+import org.sonar.server.qualityprofile.QProfileName;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexer;
+import org.sonar.server.user.index.UserQuery;
+import org.sonar.server.usergroups.DefaultGroupCreator;
+import org.sonar.server.usergroups.DefaultGroupCreatorImpl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.language.LanguageTesting.newLanguage;
+import static org.sonar.server.organization.OrganizationUpdater.NewOrganization.newOrganizationBuilder;
+
+public class OrganizationUpdaterImplTest {
+ private static final long A_DATE = 12893434L;
+ private static final String A_LOGIN = "a-login";
+ private static final String SLUG_OF_A_LOGIN = "slug-of-a-login";
+ private static final String STRING_64_CHARS = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ private static final String A_NAME = "a name";
+
+ private OrganizationUpdater.NewOrganization FULL_POPULATED_NEW_ORGANIZATION = newOrganizationBuilder()
+ .setName("a-name")
+ .setKey("a-key")
+ .setDescription("a-description")
+ .setUrl("a-url")
+ .setAvatarUrl("a-avatar")
+ .build();
+
+ private System2 system2 = new TestSystem2().setNow(A_DATE);
+
+ @Rule
+ public DbTester db = DbTester.create(system2);
+ @Rule
+ public EsTester es = EsTester.create();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public BuiltInQProfileRepositoryRule builtInQProfileRepositoryRule = new BuiltInQProfileRepositoryRule();
+
+ private DbSession dbSession = db.getSession();
+
+ private IllegalArgumentException exceptionThrownByOrganizationValidation = new IllegalArgumentException("simulate IAE thrown by OrganizationValidation");
+ private DbClient dbClient = db.getDbClient();
+ private UuidFactory uuidFactory = new SequenceUuidFactory();
+ private OrganizationValidation organizationValidation = mock(OrganizationValidation.class);
+ private MapSettings settings = new MapSettings();
+ private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
+ private UserIndex userIndex = new UserIndex(es.client(), system2);
+ private DefaultGroupCreator defaultGroupCreator = new DefaultGroupCreatorImpl(dbClient);
+ private OrganizationUpdaterImpl underTest = new OrganizationUpdaterImpl(dbClient, system2, uuidFactory, organizationValidation, settings.asConfig(), userIndexer,
+ builtInQProfileRepositoryRule, defaultGroupCreator);
+
+ @Test
+ public void create_throws_NPE_if_NewOrganization_arg_is_null() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("newOrganization can't be null");
+
+ underTest.create(dbSession, user, null);
+ }
+
+ @Test
+ public void create_throws_exception_thrown_by_checkValidKey() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+
+ when(organizationValidation.checkKey(FULL_POPULATED_NEW_ORGANIZATION.getKey()))
+ .thenThrow(exceptionThrownByOrganizationValidation);
+
+ createThrowsExceptionThrownByOrganizationValidation(user);
+ }
+
+ @Test
+ public void create_throws_exception_thrown_by_checkValidDescription() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+
+ when(organizationValidation.checkDescription(FULL_POPULATED_NEW_ORGANIZATION.getDescription())).thenThrow(exceptionThrownByOrganizationValidation);
+
+ createThrowsExceptionThrownByOrganizationValidation(user);
+ }
+
+ @Test
+ public void create_throws_exception_thrown_by_checkValidUrl() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+
+ when(organizationValidation.checkUrl(FULL_POPULATED_NEW_ORGANIZATION.getUrl())).thenThrow(exceptionThrownByOrganizationValidation);
+
+ createThrowsExceptionThrownByOrganizationValidation(user);
+ }
+
+ @Test
+ public void create_throws_exception_thrown_by_checkValidAvatar() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+
+ when(organizationValidation.checkAvatar(FULL_POPULATED_NEW_ORGANIZATION.getAvatar())).thenThrow(exceptionThrownByOrganizationValidation);
+
+ createThrowsExceptionThrownByOrganizationValidation(user);
+ }
+
+ private void createThrowsExceptionThrownByOrganizationValidation(UserDto user) throws OrganizationUpdater.KeyConflictException {
+ try {
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+ fail(exceptionThrownByOrganizationValidation + " should have been thrown");
+ } catch (IllegalArgumentException e) {
+ assertThat(e).isSameAs(exceptionThrownByOrganizationValidation);
+ }
+ }
+
+ @Test
+ public void create_fails_with_ISE_if_BuiltInQProfileRepository_has_not_been_initialized() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("initialize must be called first");
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+ }
+
+ @Test
+ public void create_fails_with_KeyConflictException_if_org_with_key_in_NewOrganization_arg_already_exists_in_db() throws OrganizationUpdater.KeyConflictException {
+ db.organizations().insertForKey(FULL_POPULATED_NEW_ORGANIZATION.getKey());
+ UserDto user = db.users().insertUser();
+
+ expectedException.expect(OrganizationUpdater.KeyConflictException.class);
+ expectedException.expectMessage("Organization key '" + FULL_POPULATED_NEW_ORGANIZATION.getKey() + "' is already used");
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+ }
+
+ @Test
+ public void create_creates_unguarded_organization_with_properties_from_NewOrganization_arg() throws OrganizationUpdater.KeyConflictException {
+ builtInQProfileRepositoryRule.initialize();
+ UserDto user = db.users().insertUser();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
+ assertThat(organization.getUuid()).isNotEmpty();
+ assertThat(organization.getKey()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getKey());
+ assertThat(organization.getName()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getName());
+ assertThat(organization.getDescription()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getDescription());
+ assertThat(organization.getUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getUrl());
+ assertThat(organization.getAvatarUrl()).isEqualTo(FULL_POPULATED_NEW_ORGANIZATION.getAvatar());
+ assertThat(organization.isGuarded()).isFalse();
+ assertThat(organization.getCreatedAt()).isEqualTo(A_DATE);
+ assertThat(organization.getUpdatedAt()).isEqualTo(A_DATE);
+ }
+
+ @Test
+ public void create_creates_owners_group_with_all_permissions_for_new_organization_and_add_current_user_to_it() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ verifyGroupOwners(user, FULL_POPULATED_NEW_ORGANIZATION.getKey(), FULL_POPULATED_NEW_ORGANIZATION.getName());
+ }
+
+ @Test
+ public void create_creates_members_group_and_add_current_user_to_it() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ verifyMembersGroup(user, FULL_POPULATED_NEW_ORGANIZATION.getKey());
+ }
+
+ @Test
+ public void create_does_not_require_description_url_and_avatar_to_be_non_null() throws OrganizationUpdater.KeyConflictException {
+ builtInQProfileRepositoryRule.initialize();
+ UserDto user = db.users().insertUser();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.create(dbSession, user, newOrganizationBuilder()
+ .setKey("key")
+ .setName("name")
+ .build());
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, "key").get();
+ assertThat(organization.getKey()).isEqualTo("key");
+ assertThat(organization.getName()).isEqualTo("name");
+ assertThat(organization.getDescription()).isNull();
+ assertThat(organization.getUrl()).isNull();
+ assertThat(organization.getAvatarUrl()).isNull();
+ assertThat(organization.isGuarded()).isFalse();
+ }
+
+ @Test
+ public void create_creates_default_template_for_new_organization() throws OrganizationUpdater.KeyConflictException {
+ builtInQProfileRepositoryRule.initialize();
+ UserDto user = db.users().insertUser();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
+ GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get();
+ int defaultGroupId = dbClient.organizationDao().getDefaultGroupId(dbSession, organization.getUuid()).get();
+ PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template");
+ assertThat(defaultTemplate.getName()).isEqualTo("Default template");
+ assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + FULL_POPULATED_NEW_ORGANIZATION.getName());
+ DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get();
+ assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid());
+ assertThat(defaultTemplates.getViewUuid()).isNull();
+ assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
+ .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
+ .containsOnly(
+ tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION),
+ tuple(defaultGroupId, UserRole.USER), tuple(defaultGroupId, UserRole.CODEVIEWER));
+ }
+
+ @Test
+ public void create_add_current_user_as_member_of_organization() throws OrganizationUpdater.KeyConflictException {
+ UserDto user = db.users().insertUser();
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ OrganizationDto result = underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ assertThat(dbClient.organizationMemberDao().select(dbSession, result.getUuid(), user.getId())).isPresent();
+ assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(result.getUuid()).setTextQuery(user.getLogin()).build(), new SearchOptions()).getTotal()).isEqualTo(1L);
+ }
+
+ @Test
+ public void create_associates_to_built_in_quality_profiles() throws OrganizationUpdater.KeyConflictException {
+ BuiltInQProfile builtIn1 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp1", true);
+ BuiltInQProfile builtIn2 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp2");
+ builtInQProfileRepositoryRule.initialize();
+ insertRulesProfile(builtIn1);
+ insertRulesProfile(builtIn2);
+ UserDto user = db.users().insertUser();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
+ List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
+ assertThat(profiles).extracting(p -> new QProfileName(p.getLanguage(), p.getName())).containsExactlyInAnyOrder(
+ builtIn1.getQProfileName(), builtIn2.getQProfileName());
+ assertThat(dbClient.qualityProfileDao().selectDefaultProfile(dbSession, organization, "foo").getName())
+ .isEqualTo("qp1");
+ }
+
+ private void insertRulesProfile(BuiltInQProfile builtIn) {
+ RulesProfileDto dto = new RulesProfileDto()
+ .setIsBuiltIn(true)
+ .setKee(RandomStringUtils.randomAlphabetic(40))
+ .setLanguage(builtIn.getLanguage())
+ .setName(builtIn.getName());
+ dbClient.qualityProfileDao().insert(db.getSession(), dto);
+ db.commit();
+ }
+
+ @Test
+ public void create_associates_to_built_in_quality_gate() throws OrganizationUpdater.KeyConflictException {
+ QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
+ builtInQProfileRepositoryRule.initialize();
+ UserDto user = db.users().insertUser();
+
+ underTest.create(dbSession, user, FULL_POPULATED_NEW_ORGANIZATION);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get();
+ assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(builtInQualityGate.getUuid());
+ }
+
+ @Test
+ public void createForUser_has_no_effect_if_setting_for_feature_is_not_set() {
+ checkSizeOfTables();
+
+ underTest.createForUser(null /* argument is not even read */, null /* argument is not even read */);
+
+ checkSizeOfTables();
+ }
+
+ @Test
+ public void createForUser_has_no_effect_if_setting_for_feature_is_disabled() {
+ enableCreatePersonalOrg(false);
+
+ checkSizeOfTables();
+
+ underTest.createForUser(null /* argument is not even read */, null /* argument is not even read */);
+
+ checkSizeOfTables();
+ }
+
+ private void checkSizeOfTables() {
+ assertThat(db.countRowsOfTable("organizations")).isEqualTo(1);
+ assertThat(db.countRowsOfTable("groups")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("groups_users")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("permission_templates")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("perm_templates_users")).isEqualTo(0);
+ assertThat(db.countRowsOfTable("perm_templates_groups")).isEqualTo(0);
+ }
+
+ @Test
+ public void createForUser_creates_guarded_organization_with_key_name_and_description_generated_from_user_login_and_name_and_associated_to_user() {
+ UserDto user = db.users().insertUser(A_LOGIN);
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(organization.getUuid()).isNotEmpty();
+ assertThat(organization.getKey()).isEqualTo(SLUG_OF_A_LOGIN);
+ assertThat(organization.getName()).isEqualTo(user.getName());
+ assertThat(organization.getDescription()).isEqualTo(user.getName() + "'s personal organization");
+ assertThat(organization.getUrl()).isNull();
+ assertThat(organization.getAvatarUrl()).isNull();
+ assertThat(organization.isGuarded()).isTrue();
+ assertThat(organization.getCreatedAt()).isEqualTo(A_DATE);
+ assertThat(organization.getUpdatedAt()).isEqualTo(A_DATE);
+
+ assertThat(db.getDbClient().userDao().selectByUuid(dbSession, user.getUuid()).getOrganizationUuid()).isEqualTo(organization.getUuid());
+ }
+
+ @Test
+ public void createForUser_fails_with_ISE_if_organization_with_slug_of_login_already_exists() {
+ UserDto user = db.users().insertUser(A_LOGIN);
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ db.organizations().insertForKey(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Can't create organization with key '" + SLUG_OF_A_LOGIN + "' " +
+ "because an organization with this key already exists");
+
+ underTest.createForUser(dbSession, user);
+ }
+
+ @Test
+ public void createForUser_gives_all_permissions_for_new_organization_to_current_user() {
+ UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, user.getId(), organization.getUuid()))
+ .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()]));
+ }
+
+ @Test
+ public void createForUser_creates_members_group_and_add_current_user_to_it() {
+ UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ verifyMembersGroup(user, SLUG_OF_A_LOGIN);
+ }
+
+ @Test
+ public void createForUser_creates_default_template_for_new_organization() {
+ UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ int defaultGroupId = dbClient.organizationDao().getDefaultGroupId(dbSession, organization.getUuid()).get();
+ PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template");
+ assertThat(defaultTemplate.getName()).isEqualTo("Default template");
+ assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + A_NAME);
+ DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get();
+ assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid());
+ assertThat(defaultTemplates.getViewUuid()).isNull();
+ assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId()))
+ .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission)
+ .containsOnly(
+ tuple(defaultGroupId, UserRole.USER), tuple(defaultGroupId, UserRole.CODEVIEWER));
+ assertThat(dbClient.permissionTemplateCharacteristicDao().selectByTemplateIds(dbSession, Collections.singletonList(defaultTemplate.getId())))
+ .extracting(PermissionTemplateCharacteristicDto::getWithProjectCreator, PermissionTemplateCharacteristicDto::getPermission)
+ .containsOnly(
+ tuple(true, UserRole.ADMIN), tuple(true, UserRole.ISSUE_ADMIN), tuple(true, GlobalPermissions.SCAN_EXECUTION));
+ }
+
+ @Test
+ public void createForUser_add_current_user_as_member_of_organization() {
+ UserDto user = db.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME));
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(dbClient.organizationMemberDao().select(dbSession, organization.getUuid(), user.getId())).isPresent();
+ }
+
+ @Test
+ public void createForUser_does_not_fail_if_name_is_too_long_for_an_organization_name() {
+ String nameTooLong = STRING_64_CHARS + "b";
+ UserDto user = db.users().insertUser(dto -> dto.setName(nameTooLong).setLogin(A_LOGIN));
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(organization.getName()).isEqualTo(STRING_64_CHARS);
+ assertThat(organization.getDescription()).isEqualTo(nameTooLong + "'s personal organization");
+ }
+
+ @Test
+ public void createForUser_does_not_fail_if_name_is_empty_and_login_is_too_long_for_an_organization_name() {
+ String login = STRING_64_CHARS + "b";
+ UserDto user = db.users().insertUser(dto -> dto.setName("").setLogin(login));
+ when(organizationValidation.generateKeyFrom(login)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(organization.getName()).isEqualTo(STRING_64_CHARS);
+ assertThat(organization.getDescription()).isEqualTo(login + "'s personal organization");
+ }
+
+ @Test
+ public void createForUser_does_not_fail_if_name_is_null_and_login_is_too_long_for_an_organization_name() {
+ String login = STRING_64_CHARS + "b";
+ UserDto user = db.users().insertUser(dto -> dto.setName(null).setLogin(login));
+ when(organizationValidation.generateKeyFrom(login)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+ db.qualityGates().insertBuiltInQualityGate();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(organization.getName()).isEqualTo(STRING_64_CHARS);
+ assertThat(organization.getDescription()).isEqualTo(login + "'s personal organization");
+ }
+
+ @Test
+ public void createForUser_associates_to_built_in_quality_profiles() {
+ UserDto user = db.users().insertUser(A_LOGIN);
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ db.qualityGates().insertBuiltInQualityGate();
+ BuiltInQProfile builtIn1 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp1");
+ BuiltInQProfile builtIn2 = builtInQProfileRepositoryRule.add(newLanguage("foo"), "qp2");
+ builtInQProfileRepositoryRule.initialize();
+ insertRulesProfile(builtIn1);
+ insertRulesProfile(builtIn2);
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization);
+ assertThat(profiles).extracting(p -> new QProfileName(p.getLanguage(), p.getName())).containsExactlyInAnyOrder(
+ builtIn1.getQProfileName(), builtIn2.getQProfileName());
+ }
+
+ @Test
+ public void createForUser_associates_to_built_in_quality_gate() {
+ QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
+ UserDto user = db.users().insertUser(A_LOGIN);
+ when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN);
+ enableCreatePersonalOrg(true);
+ builtInQProfileRepositoryRule.initialize();
+
+ underTest.createForUser(dbSession, user);
+
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get();
+ assertThat(dbClient.qualityGateDao().selectDefault(dbSession, organization).getUuid()).isEqualTo(builtInQualityGate.getUuid());
+ }
+
+ @Test
+ public void update_personal_organization() {
+ OrganizationDto organization = db.organizations().insert(o -> o.setKey("old login"));
+ when(organizationValidation.generateKeyFrom("new_login")).thenReturn("new_login");
+
+ underTest.updateOrganizationKey(dbSession, organization, "new_login");
+
+ OrganizationDto organizationReloaded = dbClient.organizationDao().selectByUuid(dbSession, organization.getUuid()).get();
+ assertThat(organizationReloaded.getKey()).isEqualTo("new_login");
+ }
+
+ @Test
+ public void does_not_update_personal_organization_when_generated_organization_key_does_not_change() {
+ OrganizationDto organization = db.organizations().insert(o -> o.setKey("login"));
+ when(organizationValidation.generateKeyFrom("Login")).thenReturn("login");
+
+ underTest.updateOrganizationKey(dbSession, organization, "Login");
+
+ OrganizationDto organizationReloaded = dbClient.organizationDao().selectByUuid(dbSession, organization.getUuid()).get();
+ assertThat(organizationReloaded.getKey()).isEqualTo("login");
+ }
+
+ @Test
+ public void fail_to_update_personal_organization_when_new_key_already_exist() {
+ OrganizationDto organization = db.organizations().insert();
+ db.organizations().insert(o -> o.setKey("new_login"));
+ when(organizationValidation.generateKeyFrom("new_login")).thenReturn("new_login");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Can't create organization with key 'new_login' because an organization with this key already exists");
+
+ underTest.updateOrganizationKey(dbSession, organization, "new_login");
+ }
+
+ private void enableCreatePersonalOrg(boolean flag) {
+ settings.setProperty(CorePropertyDefinitions.ORGANIZATIONS_CREATE_PERSONAL_ORG, flag);
+ }
+
+ private void verifyGroupOwners(UserDto user, String organizationKey, String organizationName) {
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get();
+ Optional<GroupDto> groupOpt = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners");
+ assertThat(groupOpt).isPresent();
+ GroupDto groupDto = groupOpt.get();
+ assertThat(groupDto.getDescription()).isEqualTo("Owners of organization " + organizationName);
+
+ assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId()))
+ .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()]));
+ List<UserMembershipDto> members = dbClient.groupMembershipDao().selectMembers(
+ dbSession,
+ UserMembershipQuery.builder()
+ .organizationUuid(organization.getUuid())
+ .groupId(groupDto.getId())
+ .membership(UserMembershipQuery.IN).build(),
+ 0, Integer.MAX_VALUE);
+ assertThat(members)
+ .extracting(UserMembershipDto::getLogin)
+ .containsOnly(user.getLogin());
+ }
+
+ private void verifyMembersGroup(UserDto user, String organizationKey) {
+ OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get();
+ Optional<GroupDto> groupOpt = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Members");
+ assertThat(groupOpt).isPresent();
+ GroupDto groupDto = groupOpt.get();
+ assertThat(groupDto.getDescription()).isEqualTo("All members of the organization");
+
+ assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId())).isEmpty();
+ List<UserMembershipDto> members = dbClient.groupMembershipDao().selectMembers(
+ dbSession,
+ UserMembershipQuery.builder()
+ .organizationUuid(organization.getUuid())
+ .groupId(groupDto.getId())
+ .membership(UserMembershipQuery.IN).build(),
+ 0, Integer.MAX_VALUE);
+ assertThat(members)
+ .extracting(UserMembershipDto::getLogin)
+ .containsOnly(user.getLogin());
+ }
+
+}
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.UnauthorizedException;
-import org.sonar.server.organization.OrganizationCreation;
-import org.sonar.server.organization.OrganizationCreationImpl;
+import org.sonar.server.organization.OrganizationUpdater;
+import org.sonar.server.organization.OrganizationUpdaterImpl;
import org.sonar.server.organization.OrganizationValidation;
import org.sonar.server.organization.OrganizationValidationImpl;
import org.sonar.server.organization.TestOrganizationFlags;
private OrganizationValidation organizationValidation = new OrganizationValidationImpl();
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private UserIndex userIndex = new UserIndex(es.client(), System2.INSTANCE);
- private OrganizationCreation organizationCreation = new OrganizationCreationImpl(dbClient, system2, UuidFactoryFast.getInstance(), organizationValidation, settings.asConfig(),
+ private OrganizationUpdater organizationUpdater = new OrganizationUpdaterImpl(dbClient, system2, UuidFactoryFast.getInstance(), organizationValidation, settings.asConfig(),
userIndexer,
mock(BuiltInQProfileRepository.class), new DefaultGroupCreatorImpl(dbClient));
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone().setEnabled(true);
private WsActionTester wsTester = new WsActionTester(
new CreateAction(settings.asConfig(), userSession, dbClient, new OrganizationsWsSupport(organizationValidation),
organizationValidation,
- organizationCreation, organizationFlags));
+ organizationUpdater, organizationFlags));
@Test
public void verify_define() {
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.user.index.UserIndexDefinition;
private ArgumentCaptor<NewUserHandler.Context> newUserHandler = ArgumentCaptor.forClass(NewUserHandler.Context.class);
private DbSession session = db.getSession();
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
- private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
+ private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.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,
+ private UserUpdater underTest = new UserUpdater(newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater,
new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);
@Test
.build(), u -> {
});
- verify(organizationCreation).createForUser(any(DbSession.class), eq(dto));
+ verify(organizationUpdater).createForUser(any(DbSession.class), eq(dto));
}
@Test
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.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.user.index.UserIndexer;
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 OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.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,
+ private UserUpdater underTest = new UserUpdater(newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater,
new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);
@Test
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.user.index.UserIndexDefinition;
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 OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.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,
+ private UserUpdater underTest = new UserUpdater(newUserNotifier, dbClient, userIndexer, organizationFlags, defaultOrganizationProvider, organizationUpdater,
new DefaultGroupFinder(dbClient), settings.asConfig(), localAuthentication);
@Test
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.tester.UserSessionRule;
private UserUpdater userUpdater = new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), new UserIndexer(db.getDbClient(), es.client()),
organizationFlags,
TestDefaultOrganizationProvider.from(db),
- mock(OrganizationCreation.class),
+ mock(OrganizationUpdater.class),
new DefaultGroupFinder(db.getDbClient()),
new MapSettings().asConfig(),
localAuthentication);
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.tester.UserSessionRule;
private GroupDto defaultGroupInDefaultOrg;
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone();
- private OrganizationCreation organizationCreation = mock(OrganizationCreation.class);
+ private OrganizationUpdater organizationUpdater = mock(OrganizationUpdater.class);
private LocalAuthentication localAuthentication = new LocalAuthentication(db.getDbClient());
private WsActionTester tester = new WsActionTester(new CreateAction(
db.getDbClient(),
new UserUpdater(mock(NewUserNotifier.class), db.getDbClient(), userIndexer, organizationFlags, defaultOrganizationProvider,
- organizationCreation, new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication),
+ organizationUpdater, new DefaultGroupFinder(db.getDbClient()), settings.asConfig(), localAuthentication),
userSessionRule));
@Before
assertThat(dbUser).isPresent();
ArgumentCaptor<UserDto> userCaptor = ArgumentCaptor.forClass(UserDto.class);
- verify(organizationCreation).createForUser(any(DbSession.class), userCaptor.capture());
+ verify(organizationUpdater).createForUser(any(DbSession.class), userCaptor.capture());
assertThat(userCaptor.getValue().getId()).isEqualTo(dbUser.get().getId());
}
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.organization.DefaultOrganizationProvider;
-import org.sonar.server.organization.OrganizationCreation;
+import org.sonar.server.organization.OrganizationUpdater;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
import org.sonar.server.organization.TestOrganizationFlags;
import org.sonar.server.tester.UserSessionRule;
public class UpdateActionTest {
- private static final OrganizationCreation ORGANIZATION_CREATION_NOT_USED_FOR_UPDATE = null;
+ private static final OrganizationUpdater ORGANIZATION_CREATION_NOT_USED_FOR_UPDATE = null;
private MapSettings settings = new MapSettings();
private System2 system2 = new System2();
--- /dev/null
+/*
+ * 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.
+ */
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { getIdentityProviders } from '../../../api/users';
+import * as theme from '../../../app/theme';
+import { IdentityProvider } from '../../../app/types';
+import { getTextColor } from '../../../helpers/colors';
+import { translate } from '../../../helpers/l10n';
+import { getBaseUrl } from '../../../helpers/urls';
+
+interface Props {
+ location: {
+ query: {
+ login: string;
+ providerKey: string;
+ providerName: string;
+ oldLogin: string;
+ oldOrganizationKey: string;
+ };
+ };
+}
+
+interface State {
+ identityProviders: IdentityProvider[];
+ loading: boolean;
+}
+
+export default class UpdateLogin extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = { identityProviders: [], loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchIdentityProviders();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ fetchIdentityProviders = () => {
+ this.setState({ loading: true });
+ getIdentityProviders().then(
+ ({ identityProviders }) => {
+ if (this.mounted) {
+ this.setState({ identityProviders, loading: false });
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ }
+ );
+ };
+
+ renderIdentityProvier = (provider: string, login: string) => {
+ const identityProvider = this.state.identityProviders.find(p => p.key === provider);
+
+ return identityProvider ? (
+ <div
+ className="identity-provider"
+ style={{
+ backgroundColor: identityProvider.backgroundColor,
+ color: getTextColor(identityProvider.backgroundColor, theme.secondFontColor)
+ }}>
+ <img
+ alt={identityProvider.name}
+ className="little-spacer-right"
+ src={getBaseUrl() + identityProvider.iconPath}
+ width="14"
+ height="14"
+ />
+ {login}
+ </div>
+ ) : (
+ <div>
+ {provider !== 'sonarqube' && provider} {login}
+ </div>
+ );
+ };
+
+ render() {
+ const { query } = this.props.location;
+
+ return (
+ <div id="bd" className="page-wrapper-simple">
+ <div id="nonav" className="page-simple">
+ <div className="big-spacer-bottom js-provider-name">
+ <p className="little-spacer-bottom">
+ <FormattedMessage
+ defaultMessage={translate('sessions.update_login.1')}
+ id="sessions.update_login.1"
+ values={{ providerName: <strong>{query.providerName}</strong> }}
+ />
+ </p>
+ </div>
+
+ <div className="big-spacer-bottom js-new-account">
+ <p className="little-spacer-bottom">{translate('sessions.update_login.2')}</p>
+ {this.renderIdentityProvier(query.providerKey, query.login)}
+ </div>
+
+ <div className="alert alert-warning">
+ {translate('sessions.update_login.3')}
+ <ul className="list-styled">
+ <li className="spacer-top js-old-organization-key">
+ <FormattedMessage
+ defaultMessage={translate('sessions.update_login.4')}
+ id="sessions.update_login.4"
+ values={{ organizationKey: <strong>{query.oldOrganizationKey}</strong> }}
+ />
+ </li>
+ <li className="spacer-top js-old-login">
+ <FormattedMessage
+ defaultMessage={translate('sessions.update_login.5')}
+ id="sessions.update_login.5"
+ values={{ login: <strong>{query.oldLogin}</strong> }}
+ />
+ </li>
+ </ul>
+ </div>
+
+ <div className="big-spacer-top text-right">
+ <a
+ className="button js-continue"
+ href={`${getBaseUrl()}/sessions/init/${query.providerKey}?allowUpdateLogin=true`}>
+ {translate('continue')}
+ </a>
+ <a className="big-spacer-left js-cancel" href={getBaseUrl() + '/'}>
+ {translate('cancel')}
+ </a>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+import * as React from 'react';
+import { shallow } from 'enzyme';
+import UpdateLogin from '../UpdateLogin';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+jest.mock('../../../../api/users', () => ({
+ getIdentityProviders: () =>
+ Promise.resolve({
+ identityProviders: [
+ {
+ key: 'bitbucket',
+ name: 'Bitbucket',
+ iconPath: '/static/authbitbucket/bitbucket.svg',
+ backgroundColor: '#205081'
+ },
+ {
+ key: 'github',
+ name: 'GitHub',
+ iconPath: '/static/authgithub/github.svg',
+ backgroundColor: '#444444'
+ }
+ ]
+ })
+}));
+
+it('render', async () => {
+ const query = {
+ login: 'foo',
+ providerKey: 'github',
+ providerName: 'GitHub',
+ oldLogin: 'old_foo',
+ oldOrganizationKey: 'old_foo@github'
+ };
+ const wrapper = shallow(<UpdateLogin location={{ query }} />);
+ (wrapper.instance() as UpdateLogin).mounted = true;
+ (wrapper.instance() as UpdateLogin).fetchIdentityProviders();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`render 1`] = `
+<div
+ className="page-wrapper-simple"
+ id="bd"
+>
+ <div
+ className="page-simple"
+ id="nonav"
+ >
+ <div
+ className="big-spacer-bottom js-provider-name"
+ >
+ <p
+ className="little-spacer-bottom"
+ >
+ <FormattedMessage
+ defaultMessage="sessions.update_login.1"
+ id="sessions.update_login.1"
+ values={
+ Object {
+ "providerName": <strong>
+ GitHub
+ </strong>,
+ }
+ }
+ />
+ </p>
+ </div>
+ <div
+ className="big-spacer-bottom js-new-account"
+ >
+ <p
+ className="little-spacer-bottom"
+ >
+ sessions.update_login.2
+ </p>
+ <div
+ className="identity-provider"
+ style={
+ Object {
+ "backgroundColor": "#444444",
+ "color": "#fff",
+ }
+ }
+ >
+ <img
+ alt="GitHub"
+ className="little-spacer-right"
+ height="14"
+ src="/static/authgithub/github.svg"
+ width="14"
+ />
+ foo
+ </div>
+ </div>
+ <div
+ className="alert alert-warning"
+ >
+ sessions.update_login.3
+ <ul
+ className="list-styled"
+ >
+ <li
+ className="spacer-top js-old-organization-key"
+ >
+ <FormattedMessage
+ defaultMessage="sessions.update_login.4"
+ id="sessions.update_login.4"
+ values={
+ Object {
+ "organizationKey": <strong>
+ old_foo@github
+ </strong>,
+ }
+ }
+ />
+ </li>
+ <li
+ className="spacer-top js-old-login"
+ >
+ <FormattedMessage
+ defaultMessage="sessions.update_login.5"
+ id="sessions.update_login.5"
+ values={
+ Object {
+ "login": <strong>
+ old_foo
+ </strong>,
+ }
+ }
+ />
+ </li>
+ </ul>
+ </div>
+ <div
+ className="big-spacer-top text-right"
+ >
+ <a
+ className="button js-continue"
+ href="/sessions/init/github?allowUpdateLogin=true"
+ >
+ continue
+ </a>
+ <a
+ className="big-spacer-left js-cancel"
+ href="/"
+ >
+ cancel
+ </a>
+ </div>
+ </div>
+</div>
+`;
{
path: 'email_already_exists',
component: lazyLoad(() => import('./components/EmailAlreadyExists'))
+ },
+ {
+ path: 'update_login',
+ component: lazyLoad(() => import('./components/UpdateLogin'))
}
];
sessions.email_already_exists.4=Your email address will be erased from the first account.
sessions.email_already_exists.5=You will no longer receive email notifications from this account.
sessions.email_already_exists.6=Issues won't be automatically assigned to this account anymore.
-
+sessions.update_login.1=We noticed that you've renamed your username on {providerName}.
+sessions.update_login.2=By clicking on "Continue" we will update the key of your personal organization and rename your login to :
+sessions.update_login.3=This means that you need to update :
+sessions.update_login.4=Build scripts that might be using your old personal organization key ({organizationKey}).
+sessions.update_login.5=Any Web Service call that might be referencing your old login ({login}).
#------------------------------------------------------------------------------
#
@Test
public void update_login() {
enablePlugin();
- String oldLogin = "login_to_update@base";
- setUserCreatedByAuthPlugin(oldLogin, "login_to_update_id", "old_provider_login", USER_NAME, USER_EMAIL);
+ String oldLogin = tester.users().generateLogin();
+ String providerId = tester.users().generateProviderId();
+ setUserCreatedByAuthPlugin(oldLogin, providerId, tester.users().generateLogin(), USER_NAME, USER_EMAIL);
assertThat(tester.users().getByLogin(USER_LOGIN)).isNotPresent();
authenticateWithFakeAuthProvider();
// Login is updated
- String newLogin = "new_login@base";
- setUserCreatedByAuthPlugin(newLogin, "login_to_update_id", "new_provider_login", USER_NAME, USER_EMAIL);
+ String newLogin = tester.users().generateLogin();
+ String newProviderLogin = tester.users().generateLogin();
+ setUserCreatedByAuthPlugin(newLogin, providerId, newProviderLogin, USER_NAME, USER_EMAIL);
authenticateWithFakeAuthProvider();
assertThat(tester.users().getByLogin(oldLogin)).isNotPresent();
assertThat(tester.users().service().search(new SearchRequest().setQ(newLogin)).getUsersList())
.extracting(User::getLogin, User::getName, User::getEmail, User::getExternalProvider, User::getExternalIdentity, User::getLocal)
- .containsExactlyInAnyOrder(tuple(newLogin, USER_NAME, USER_EMAIL, FAKE_PROVIDER_KEY, "new_provider_login", false));
+ .containsExactlyInAnyOrder(tuple(newLogin, USER_NAME, USER_EMAIL, FAKE_PROVIDER_KEY, newProviderLogin, false));
// Check that searching for old login return nothing
assertThat(tester.users().service().search(new SearchRequest().setQ(oldLogin)).getPaging().getTotal()).isZero();
}
@Test
public void authenticate_with_external_id_null() {
enablePlugin();
- String login = "no_external_id_login";
- setUserCreatedByAuthPlugin(login, null, "no_external_id_provider_login", USER_NAME, USER_EMAIL);
+ String login = tester.users().generateLogin();
+ setUserCreatedByAuthPlugin(login, null, login, USER_NAME, USER_EMAIL);
// First authentication
authenticateWithFakeAuthProvider();
@Test
public void update_external_id() {
enablePlugin();
- String login = "update_external_id_login";
- setUserCreatedByAuthPlugin(login, "old_external_id", login, USER_NAME, USER_EMAIL);
+ String login = tester.users().generateLogin();
+ setUserCreatedByAuthPlugin(login, tester.users().generateProviderId(), login, USER_NAME, USER_EMAIL);
authenticateWithFakeAuthProvider();
assertThat(tester.users().getByLogin(login)).isPresent();
- setUserCreatedByAuthPlugin(login, "new_external_id", login, USER_NAME, USER_EMAIL);
+ setUserCreatedByAuthPlugin(login, tester.users().generateProviderId(), login, USER_NAME, USER_EMAIL);
authenticateWithFakeAuthProvider();
assertThat(tester.users().getByLogin(login)).isPresent();
}
}
private void setUserCreatedByAuthPlugin(String login, @Nullable String providerId, String providerLogin, String name, String email) {
- tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login + "," + (providerId == null ? "" : providerId) + "," + providerLogin + "," + name + "," + email);
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user",
+ login + "," + (providerId == null ? "" : providerId) + "," + providerLogin + "," + name + "," + email);
}
private void setGroupsReturnedByAuthPlugin(String... groups) {
public void update_login() {
simulateRedirectionToCallback();
enablePlugin();
+ String providerId = tester.users().generateProviderId();
- String oldLogin = "login_to_update@oauth2";
- tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", oldLogin + "," + "login_to_update_id" + "," + "old_provider_login" + "," + USER_NAME + "," + USER_EMAIL);
+ String oldLogin = tester.users().generateLogin();
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user",
+ oldLogin + "," + providerId + "," + tester.users().generateLogin() + "," + USER_NAME + "," + USER_EMAIL);
authenticateWithFakeAuthProvider();
- String newLogin = "new_logine@oauth2";
- tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", newLogin + "," + "login_to_update_id" + "," + "new_provider_login" + "," + USER_NAME + "," + USER_EMAIL);
+ String newLogin = tester.users().generateLogin();
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user",
+ newLogin + "," + providerId + "," + tester.users().generateLogin() + "," + USER_NAME + "," + USER_EMAIL);
authenticateWithFakeAuthProvider();
verifyUser(newLogin, USER_NAME, USER_EMAIL);
private void enablePlugin() {
tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.enabled", "true");
tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.url", fakeServerAuthProviderUrl);
- tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", USER_LOGIN + "," + USER_PROVIDER_ID + "," + USER_PROVIDER_LOGIN + "," + USER_NAME + "," + USER_EMAIL);
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user",
+ USER_LOGIN + "," + USER_PROVIDER_ID + "," + USER_PROVIDER_LOGIN + "," + USER_NAME + "," + USER_EMAIL);
}
private void assertThatUserDoesNotExist(String login) {
--- /dev/null
+/*
+ * 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.sonarqube.tests.user;
+
+import com.google.common.base.Joiner;
+import com.sonar.orchestrator.Orchestrator;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.UserGroups.Group;
+import org.sonarqube.ws.Users.CreateWsResponse.User;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.organizations.AddMemberRequest;
+
+public class OrganizationBaseIdentityProviderTest {
+
+ @ClassRule
+ public static Orchestrator orchestrator = SonarCloudUserSuite.ORCHESTRATOR;
+
+ @Rule
+ public Tester tester = new Tester(orchestrator);
+
+ @Before
+ public void setUp() {
+ // enable the fake authentication plugin
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabled", "true");
+ }
+
+ @After
+ public void tearDown() {
+ tester.settings().resetSettings("sonar.auth.fake-base-id-provider.enabled",
+ "sonar.auth.fake-base-id-provider.user",
+ "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage",
+ "sonar.auth.fake-base-id-provider.enabledGroupsSync",
+ "sonar.auth.fake-base-id-provider.groups",
+ "sonar.auth.fake-base-id-provider.allowsUsersToSignUp");
+ }
+
+ @Test
+ public void synchronize_groups_for_new_user() {
+ Group group = tester.groups().generate();
+ String login = tester.users().generateLogin();
+ enableUserCreationByAuthPlugin(login);
+ setGroupsReturnedByAuthPlugin(group.getName());
+
+ authenticateWithFakeAuthProvider();
+
+ // No default group membership
+ tester.groups().assertThatUserIsOnlyMemberOf(null, login, group.getName());
+ }
+
+ @Test
+ public void synchronize_groups_for_existing_user() {
+ Group group = tester.groups().generate();
+ User user = tester.users().generate();
+ enableUserCreationByAuthPlugin(user.getLogin());
+ setGroupsReturnedByAuthPlugin(group.getName());
+
+ authenticateWithFakeAuthProvider();
+
+ // No default group membership
+ tester.groups().assertThatUserIsOnlyMemberOf(null, user.getLogin(), group.getName());
+ }
+
+ @Test
+ public void remove_default_group() {
+ Group group = tester.groups().generate();
+ User user = tester.users().generate();
+ // Add user as member of default organization
+ tester.wsClient().organizations().addMember(new AddMemberRequest().setOrganization("default-organization").setLogin(user.getLogin()));
+ tester.groups().assertThatUserIsMemberOf(null, user.getLogin(), "Members");
+ enableUserCreationByAuthPlugin(user.getLogin());
+ // No group is returned by the plugin
+ setGroupsReturnedByAuthPlugin();
+
+ authenticateWithFakeAuthProvider();
+
+ // No default group membership
+ tester.groups().assertThatUserIsOnlyMemberOf(null, user.getLogin());
+ }
+
+ private void enableUserCreationByAuthPlugin(String login) {
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login + "," + login + ",fake-" + login + ",John,john@email.com");
+ }
+
+ private void setGroupsReturnedByAuthPlugin(String... groups) {
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabledGroupsSync", "true");
+ if (groups.length > 0) {
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.groups", Joiner.on(",").join(groups));
+ }
+ }
+
+ private void authenticateWithFakeAuthProvider() {
+ tester.wsClient().wsConnector().call(
+ new GetRequest("/sessions/init/fake-base-id-provider"))
+ .failIfNotSuccessful();
+ }
+
+}
+++ /dev/null
-/*
- * 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.sonarqube.tests.user;
-
-import com.google.common.base.Joiner;
-import com.sonar.orchestrator.Orchestrator;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonarqube.qa.util.Tester;
-import org.sonarqube.ws.UserGroups.Group;
-import org.sonarqube.ws.Users.CreateWsResponse.User;
-import org.sonarqube.ws.client.GetRequest;
-import org.sonarqube.ws.client.organizations.AddMemberRequest;
-
-public class OrganizationIdentityProviderTest {
-
- @ClassRule
- public static Orchestrator orchestrator = SonarCloudUserSuite.ORCHESTRATOR;
-
- @Rule
- public Tester tester = new Tester(orchestrator);
-
- @Before
- public void setUp() {
- // enable the fake authentication plugin
- tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabled", "true");
- }
-
- @After
- public void tearDown() {
- tester.settings().resetSettings("sonar.auth.fake-base-id-provider.enabled",
- "sonar.auth.fake-base-id-provider.user",
- "sonar.auth.fake-base-id-provider.throwUnauthorizedMessage",
- "sonar.auth.fake-base-id-provider.enabledGroupsSync",
- "sonar.auth.fake-base-id-provider.groups",
- "sonar.auth.fake-base-id-provider.allowsUsersToSignUp");
- }
-
- @Test
- public void default_group_is_not_added_for_new_user() {
- Group group = tester.groups().generate();
- enableUserCreationByAuthPlugin("aLogin");
- setGroupsReturnedByAuthPlugin(group.getName());
-
- authenticateWithFakeAuthProvider();
-
- // No default group membership
- tester.groups().assertThatUserIsOnlyMemberOf(null, "aLogin", group.getName());
- }
-
- @Test
- public void default_group_is_not_sync_for_existing_user() {
- Group group = tester.groups().generate();
- User user = tester.users().generate();
- enableUserCreationByAuthPlugin(user.getLogin());
- setGroupsReturnedByAuthPlugin(group.getName());
-
- authenticateWithFakeAuthProvider();
-
- // No default group membership
- tester.groups().assertThatUserIsOnlyMemberOf(null, user.getLogin(), group.getName());
- }
-
- @Test
- public void remove_default_group() {
- Group group = tester.groups().generate();
- User user = tester.users().generate();
- // Add user as member of default organization
- tester.wsClient().organizations().addMember(new AddMemberRequest().setOrganization("default-organization").setLogin(user.getLogin()));
- tester.groups().assertThatUserIsMemberOf(null, user.getLogin(), "Members");
- enableUserCreationByAuthPlugin(user.getLogin());
- // No group is returned by the plugin
- setGroupsReturnedByAuthPlugin();
-
- authenticateWithFakeAuthProvider();
-
- // No default group membership
- tester.groups().assertThatUserIsOnlyMemberOf(null, user.getLogin());
- }
-
- private void enableUserCreationByAuthPlugin(String login) {
- tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login +"," + login + ",fake-" + login + ",John,john@email.com");
- }
-
- private void setGroupsReturnedByAuthPlugin(String... groups) {
- tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabledGroupsSync", "true");
- if (groups.length > 0) {
- tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.groups", Joiner.on(",").join(groups));
- }
- }
-
- private void authenticateWithFakeAuthProvider() {
- tester.wsClient().wsConnector().call(
- new GetRequest("/sessions/init/fake-base-id-provider"))
- .failIfNotSuccessful();
- }
-
-}
--- /dev/null
+/*
+ * 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.sonarqube.tests.user;
+
+import com.sonar.orchestrator.Orchestrator;
+import java.net.HttpURLConnection;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.Organizations;
+import org.sonarqube.ws.Users;
+import org.sonarqube.ws.client.organizations.SearchRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SonarCloudOAuth2IdentityProviderTest {
+
+ @ClassRule
+ public static Orchestrator orchestrator = SonarCloudUserSuite.ORCHESTRATOR;
+
+ private static String FAKE_PROVIDER_KEY = "fake-oauth2-id-provider";
+
+ @Rule
+ public Tester tester = new Tester(orchestrator);
+
+ private MockWebServer fakeServerAuthProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ fakeServerAuthProvider = new MockWebServer();
+ fakeServerAuthProvider.start();
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.enabled", "true");
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.url", fakeServerAuthProvider.url("").url().toString());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ tester.settings().resetSettings(
+ "sonar.auth.fake-oauth2-id-provider.enabled",
+ "sonar.auth.fake-oauth2-id-provider.url",
+ "sonar.auth.fake-oauth2-id-provider.user");
+ fakeServerAuthProvider.shutdown();
+ }
+
+ @Test
+ public void user_is_redirect_to_warning_page_when_updating_login_and_personal_organization_key() {
+ String oldLogin = tester.users().generateLogin();
+ String newLogin = tester.users().generateLogin();
+ String providerId = tester.users().generateProviderId();
+ tester.settings().setGlobalSettings("sonar.organizations.createPersonalOrg", "true");
+
+ // First authentication to create the user with its personal organization
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", oldLogin + "," + providerId + ",fake-" + oldLogin + ",John,john@email.com");
+ simulateRedirectionToCallback();
+ tester.openBrowser().openLogin().useOAuth2().shouldBeLoggedIn();
+ assertThat(tester.organizations().service().search(new SearchRequest()).getOrganizationsList())
+ .extracting(Organizations.Organization::getKey)
+ .contains(oldLogin);
+
+ // Second authentication, login is updated
+ tester.settings().setGlobalSettings("sonar.auth.fake-oauth2-id-provider.user", newLogin + "," + providerId + ",fake-" + newLogin + ",John,john@email.com");
+ simulateRedirectionToCallback();
+ tester.openBrowser()
+ .logIn()
+ .useOAuth2()
+ .asUpdateLoginPage()
+ .shouldHaveProviderName("Fake oauth2 identity provider")
+ .shouldHaveNewAccount("fake-" + newLogin)
+ .shouldHaveOldLogin(oldLogin)
+ .shouldHaveOldOrganizationKey(oldLogin)
+ .clickContinue();
+
+ assertThat(tester.users().service().search(new org.sonarqube.ws.client.users.SearchRequest()).getUsersList())
+ .extracting(Users.SearchWsResponse.User::getLogin)
+ .contains(newLogin)
+ .doesNotContainSequence(oldLogin);
+ assertThat(tester.organizations().service().search(new SearchRequest()).getOrganizationsList())
+ .extracting(Organizations.Organization::getKey)
+ .contains(newLogin)
+ .doesNotContain(oldLogin);
+ }
+
+ private void simulateRedirectionToCallback() {
+ fakeServerAuthProvider.enqueue(new MockResponse()
+ .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+ .addHeader("Location: " + orchestrator.getServer().getUrl() + "/oauth2/callback/" + FAKE_PROVIDER_KEY)
+ .setBody("Redirect to SonarQube"));
+ fakeServerAuthProvider.enqueue(new MockResponse()
+ .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
+ .addHeader("Location: " + orchestrator.getServer().getUrl() + "/oauth2/callback/" + FAKE_PROVIDER_KEY)
+ .setBody("Redirect to SonarQube"));
+ }
+
+}
--- /dev/null
+/*
+ * 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.sonarqube.tests.user;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Organizations.Organization;
+import org.sonarqube.ws.Projects;
+import org.sonarqube.ws.Qualityprofiles;
+import org.sonarqube.ws.Settings;
+import org.sonarqube.ws.Users;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.organizations.AddMemberRequest;
+import org.sonarqube.ws.client.organizations.SearchRequest;
+import org.sonarqube.ws.client.settings.SetRequest;
+import org.sonarqube.ws.client.settings.ValuesRequest;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.projectDir;
+
+public class SonarCloudUpdateLoginDuringAuthenticationTest {
+
+ @ClassRule
+ public static Orchestrator orchestrator = SonarCloudUserSuite.ORCHESTRATOR;
+
+ @Rule
+ public Tester tester = new Tester(orchestrator);
+
+ @Before
+ public void setUp() {
+ // enable the fake authentication plugin
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.enabled", "true");
+ }
+
+ @After
+ public void tearDown() {
+ tester.settings().resetSettings("sonar.auth.fake-base-id-provider.enabled", "sonar.auth.fake-base-id-provider.user");
+ }
+
+ @Test
+ public void update_login_and_personal_organization_when_authenticating_existing_user() {
+ tester.settings().setGlobalSettings("sonar.organizations.createPersonalOrg", "true");
+ String oldLogin = tester.users().generateLogin();
+ String providerId = tester.users().generateProviderId();
+ authenticate(oldLogin, providerId);
+ assertThat(tester.organizations().service().search(new SearchRequest()).getOrganizationsList())
+ .extracting(Organization::getKey)
+ .contains(oldLogin);
+
+ String newLogin = tester.users().generateLogin();
+ authenticate(newLogin, providerId);
+
+ assertThat(tester.users().service().search(new org.sonarqube.ws.client.users.SearchRequest()).getUsersList())
+ .extracting(Users.SearchWsResponse.User::getLogin)
+ .contains(newLogin)
+ .doesNotContainSequence(oldLogin);
+ assertThat(tester.organizations().service().search(new SearchRequest()).getOrganizationsList())
+ .extracting(Organization::getKey)
+ .contains(newLogin)
+ .doesNotContain(oldLogin);
+ }
+
+ @Test
+ public void update_login_and_personal_organization_during_auth_by_updating_only_one_letter_from_lower_case_to_upper_case() {
+ String baseLogin = tester.users().generateLogin();
+ String loginHavingLowerCase = baseLogin + "_letter";
+ String providerId = tester.users().generateProviderId();
+ authenticate(loginHavingLowerCase, providerId);
+ assertThat(tester.organizations().service().search(new SearchRequest()).getOrganizationsList())
+ .extracting(Organization::getKey)
+ .contains(loginHavingLowerCase);
+
+ String loginHavingUpperCase = baseLogin + "_Letter";
+ authenticate(loginHavingUpperCase, providerId);
+
+ assertThat(tester.users().service().search(new org.sonarqube.ws.client.users.SearchRequest()).getUsersList())
+ .extracting(Users.SearchWsResponse.User::getLogin)
+ .contains(loginHavingUpperCase)
+ .doesNotContainSequence(loginHavingLowerCase);
+ assertThat(tester.organizations().service().search(new SearchRequest()).getOrganizationsList())
+ .extracting(Organization::getKey)
+ // Organization key is still using key having lowercase
+ .contains(loginHavingLowerCase)
+ .doesNotContain(loginHavingUpperCase);
+ }
+
+ @Test
+ public void default_assignee_login_is_updated_after_login_update() {
+ String oldLogin = tester.users().generateLogin();
+ String providerId = tester.users().generateProviderId();
+
+ // Create user using authentication, and set user as member of the organization
+ authenticate(oldLogin, providerId);
+ Organization organization = tester.organizations().generate();
+ tester.organizations().service().addMember(new AddMemberRequest().setOrganization(organization.getKey()).setLogin(oldLogin));
+
+ // Set default assignee on project, and execute analysis
+ Projects.CreateWsResponse.Project project = tester.projects().provision(organization);
+ tester.wsClient().settings().set(new SetRequest().setKey("sonar.issues.defaultAssigneeLogin").setComponent(project.getKey()).setValue(oldLogin));
+ Qualityprofiles.CreateWsResponse.QualityProfile profile = tester.qProfiles().createXooProfile(organization);
+ tester.qProfiles().assignQProfileToProject(profile, project);
+ tester.qProfiles().activateRule(profile.getKey(), "xoo:OneIssuePerLine");
+ orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample"),
+ "sonar.organization", organization.getKey(),
+ "sonar.projectKey", project.getKey(),
+ "sonar.login", "admin",
+ "sonar.password", "admin"));
+ tester.wsClient().issues().search(new org.sonarqube.ws.client.issues.SearchRequest().setOrganization(organization.getKey())).getIssuesList()
+ .forEach(i -> assertThat(getIssue(organization, i.getKey()).getAssignee()).isEqualTo(oldLogin));
+
+ // Update login during authentication, check new login is the default assignee
+ String newLogin = tester.users().generateLogin();
+ authenticate(newLogin, providerId);
+ assertThat(tester.wsClient().settings().values(new ValuesRequest().setKeys(singletonList("sonar.issues.defaultAssigneeLogin")).setComponent(project.getKey()))
+ .getSettingsList())
+ .extracting(Settings.Setting::getValue)
+ .containsExactlyInAnyOrder(newLogin);
+ }
+
+ private void authenticate(String login, String providerId) {
+ tester.settings().setGlobalSettings("sonar.auth.fake-base-id-provider.user", login + "," + providerId + ",fake-" + login + ",John,john@email.com");
+ tester.wsClient().wsConnector().call(
+ new GetRequest("/sessions/init/fake-base-id-provider"))
+ .failIfNotSuccessful();
+ }
+
+ private Issue getIssue(Organization organization, String issueKey) {
+ List<Issue> issues = tester.wsClient().issues().search(new org.sonarqube.ws.client.issues.SearchRequest()
+ .setIssues(singletonList(issueKey))
+ .setOrganization(organization.getKey())
+ .setAdditionalFields(singletonList("comments")))
+ .getIssuesList();
+ assertThat(issues).hasSize(1);
+ return issues.get(0);
+ }
+
+}
@RunWith(Suite.class)
@Suite.SuiteClasses({
- OrganizationIdentityProviderTest.class,
+ OrganizationBaseIdentityProviderTest.class,
SonarCloudHomepageTest.class,
- SonarCloudNotificationsWsTest.class
+ SonarCloudNotificationsWsTest.class,
+ SonarCloudOAuth2IdentityProviderTest.class,
+ SonarCloudUpdateLoginDuringAuthenticationTest.class
})
public class SonarCloudUserSuite {
public static final Orchestrator ORCHESTRATOR = newOrchestratorBuilder()
.addPlugin(xooPlugin())
- // Used by OrganizationIdentityProviderTest
+ // Used by OrganizationBaseIdentityProviderTest
.addPlugin(pluginArtifact("base-auth-plugin"))
+ // Used in OrganizationOAuth2IdentityProviderTest
+ .addPlugin(pluginArtifact("oauth2-auth-plugin"))
+
.setServerProperty("sonar.sonarcloud.enabled", "true")
+ .setServerProperty("sonar.organizations.createPersonalOrg", "true")
// reduce memory for Elasticsearch
.setServerProperty("sonar.search.javaOpts", "-Xms128m -Xmx128m")
+ .setServerProperty("sonar.web.javaAdditionalOpts", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001")
.build();