diff options
author | Eric Hartmann <hartmann.eric@gmail.com> | 2017-09-13 17:23:33 +0200 |
---|---|---|
committer | Eric Hartmann <hartmann.eric@gmail.Com> | 2017-10-02 13:03:35 +0200 |
commit | 803439a16e9b40da4ad9a5baa9cd84a407c607b5 (patch) | |
tree | 83aa6b7227ed952357f60dea13edb24623359dec /server/sonar-db-dao | |
parent | 0000edfd89ee6084a0cde4a08424b3bad346e69e (diff) | |
download | sonarqube-803439a16e9b40da4ad9a5baa9cd84a407c607b5.tar.gz sonarqube-803439a16e9b40da4ad9a5baa9cd84a407c607b5.zip |
SONAR-4824 Prevent notification when user does not have Browse permission
Diffstat (limited to 'server/sonar-db-dao')
8 files changed, 179 insertions, 121 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java index d92a139a1b5..56ff394b3eb 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java @@ -177,6 +177,13 @@ public class AuthorizationDao implements Dao { return mapper(dbSession).selectLoginsWithGlobalPermission(ADMINISTER.getKey()); } + public Set<String> keepAuthorizedLoginsOnProject(DbSession dbSession, Set<String> logins, String projectUuid, String permission) { + return executeLargeInputsIntoSet( + logins, + partitionOfLogins -> mapper(dbSession).keepAuthorizedLoginsOnProject(partitionOfLogins, projectUuid, permission), + partitionSize -> partitionSize / 3); + } + private static AuthorizationMapper mapper(DbSession dbSession) { return dbSession.getMapper(AuthorizationMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationMapper.java index da8ecbb5356..1c788ea1019 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationMapper.java @@ -61,5 +61,9 @@ public interface AuthorizationMapper { Set<String> selectProjectPermissionsOfAnonymous(@Param("projectUuid") String projectUuid); + List<String> selectQualityProfileAdministratorLogins(@Param("permission") String permission); + + Set<String> keepAuthorizedLoginsOnProject(@Param("logins") List<String> logins, @Param("projectUuid") String projectUuid, @Param("permission") String permission); + List<String> selectLoginsWithGlobalPermission(@Param("permission") String permission); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java index f12f1027cfb..4ee31513608 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java @@ -32,6 +32,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; import org.sonar.db.Dao; import org.sonar.db.DbSession; import org.sonar.db.MyBatis; @@ -60,20 +61,17 @@ public class PropertiesDao implements Dao { * Returns the logins of users who have subscribed to the given notification dispatcher with the given notification channel. * If a resource ID is passed, the search is made on users who have specifically subscribed for the given resource. * + * Note that {@link UserRole#USER} permission is not checked here, filter the results with + * {@link org.sonar.db.permission.AuthorizationDao#keepAuthorizedLoginsOnProject} + * * @return the list of logins (maybe be empty - obviously) */ - public List<String> selectUsersForNotification(String notificationDispatcherKey, String notificationChannelKey, @Nullable String projectUuid) { + public Set<String> findUsersForNotification(String notificationDispatcherKey, String notificationChannelKey, @Nullable String projectUuid) { try (DbSession session = mybatis.openSession(false)) { return getMapper(session).findUsersForNotification(NOTIFICATION_PREFIX + notificationDispatcherKey + "." + notificationChannelKey, projectUuid); } } - public List<String> selectNotificationSubscribers(String notificationDispatcherKey, String notificationChannelKey, @Nullable String componentKey) { - try (DbSession session = mybatis.openSession(false)) { - return getMapper(session).findNotificationSubscribers(NOTIFICATION_PREFIX + notificationDispatcherKey + "." + notificationChannelKey, componentKey); - } - } - public boolean hasProjectNotificationSubscribersForDispatchers(String projectUuid, Collection<String> dispatcherKeys) { try (DbSession session = mybatis.openSession(false); Connection connection = session.getConnection(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java index ac422e3cd1d..b15395951f9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java @@ -20,14 +20,13 @@ package org.sonar.db.property; import java.util.List; +import java.util.Set; import javax.annotation.Nullable; import org.apache.ibatis.annotations.Param; public interface PropertiesMapper { - List<String> findUsersForNotification(@Param("notifKey") String notificationKey, @Nullable @Param("projectUuid") String projectUuid); - - List<String> findNotificationSubscribers(@Param("propKey") String propertyKey, @Nullable @Param("componentKey") String componentKey); + Set<String> findUsersForNotification(@Param("notifKey") String notificationKey, @Nullable @Param("projectUuid") String projectUuid); List<PropertyDto> selectGlobalProperties(); diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml index 072fefe4e3d..b43ede52e69 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/permission/AuthorizationMapper.xml @@ -396,4 +396,39 @@ gr.role = #{permission,jdbcType=VARCHAR} and gr.group_id is not null </select> + + <select id="keepAuthorizedLoginsOnProject" parameterType="map" resultType="String"> + SELECT u.login + FROM users u + INNER JOIN user_roles ur ON ur.user_id = u.id + INNER JOIN projects p ON p.uuid = #{projectUuid,jdbcType=VARCHAR} + WHERE + ur.organization_uuid = p.organization_uuid + AND ur.resource_id = p.id + AND ur.role = #{permission,jdbcType=VARCHAR} + AND u.login IN <foreach collection="logins" open="(" close=")" item="login" separator=",">#{login}</foreach> + + UNION + + SELECT u.login + FROM users u + INNER JOIN projects p ON p.uuid = #{projectUuid,jdbcType=VARCHAR} + INNER JOIN group_roles gr ON gr.organization_uuid = p.organization_uuid + INNER JOIN groups_users gu ON gr.group_id = gu.group_id + WHERE + gu.user_id = u.id + AND gr.role = #{permission,jdbcType=VARCHAR} + AND u.login IN <foreach collection="logins" open="(" close=")" item="login" separator=",">#{login}</foreach> + + <if test="permission == 'user' or permission == 'codeviewer'"> + UNION + + SELECT u.login + FROM users u + INNER JOIN projects p ON p.uuid = #{projectUuid,jdbcType=VARCHAR} + WHERE + p.private = ${_false} + AND u.login IN <foreach collection="logins" open="(" close=")" item="login" separator=",">#{login}</foreach> + </if> + </select> </mapper> diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml index a9a520eb1ee..43c4ee3a8d7 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml @@ -3,51 +3,30 @@ <mapper namespace="org.sonar.db.property.PropertiesMapper"> - <select id="findUsersForNotification" parameterType="map" resultType="String"> - select + <select id="findUsersForNotification" parameterType="map" resultType="java.lang.String"> + SELECT u.login - from + FROM users u - inner join properties p on - p.user_id=u.id - <choose> - <when test="projectUuid == null"> - where - p.prop_key = #{notifKey,jdbcType=VARCHAR} - and p.text_value = 'true' - and p.resource_id is null - </when> - <otherwise> - inner join projects c on - c.id=p.resource_id - where - p.prop_key = #{notifKey,jdbcType=VARCHAR} - and p.text_value = 'true' - and c.uuid = #{projectUuid,jdbcType=VARCHAR} - and p.resource_id is not null - </otherwise> - </choose> - </select> + INNER JOIN properties p ON p.user_id = u.id + WHERE + p.prop_key = #{notifKey,jdbcType=VARCHAR} + AND p.text_value = 'true' + AND p.resource_id IS NULL - <select id="findNotificationSubscribers" parameterType="map" resultType="String"> - select + UNION + + SELECT u.login - from - properties p - inner join users u on - p.user_id = u.id - <if test="componentKey != null"> - left outer join projects p1 on p.resource_id=p1.id and p1.kee=#{componentKey,jdbcType=VARCHAR} - </if> - where - p.prop_key = #{propKey,jdbcType=VARCHAR} - and p.text_value like 'true' - and ( - p.resource_id is null - <if test="componentKey != null"> - or p.resource_id=p1.id - </if> - ) + FROM + users u + INNER JOIN projects c on c.uuid = #{projectUuid,jdbcType=VARCHAR} + INNER JOIN properties p ON p.user_id = u.id + WHERE + p.prop_key = #{notifKey,jdbcType=VARCHAR} + AND p.text_value = 'true' + AND p.resource_id = c.id + </select> <sql id="columnsToScrapPropertyDto"> diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/permission/AuthorizationDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/permission/AuthorizationDaoTest.java index 36595bc5d72..693266f6722 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/permission/AuthorizationDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/permission/AuthorizationDaoTest.java @@ -1028,4 +1028,69 @@ public class AuthorizationDaoTest { assertThat(logins).containsExactlyInAnyOrder(user1.getLogin(), user2.getLogin(), user3.getLogin()); } + @Test + public void keepAuthorizedLoginsOnProject_return_correct_users_on_public_project() { + ComponentDto project = db.components().insertPublicProject(organization); + + UserDto user1 = db.users().insertUser(); + + // admin with "direct" ADMIN role + UserDto admin1 = db.users().insertUser(); + db.users().insertProjectPermissionOnUser(admin1, UserRole.ADMIN, project); + + // admin2 with ADMIN role through group + UserDto admin2 = db.users().insertUser(); + GroupDto adminGroup = db.users().insertGroup(organization, "ADMIN"); + db.users().insertMember(adminGroup, admin2); + db.users().insertProjectPermissionOnGroup(adminGroup, UserRole.ADMIN, project); + + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, newHashSet(user1.getLogin()), project.uuid(), UserRole.USER)) + .containsOnly(user1.getLogin()); + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, newHashSet(user1.getLogin(), admin1.getLogin(), admin2.getLogin()), project.uuid(), UserRole.USER)) + .containsOnly(user1.getLogin(), admin1.getLogin(), admin2.getLogin()); + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, newHashSet(user1.getLogin(), admin1.getLogin(), admin2.getLogin()), project.uuid(), UserRole.ADMIN)) + .containsOnly(admin1.getLogin(), admin2.getLogin()); + } + + @Test + public void keepAuthorizedLoginsOnProject_return_correct_users_on_private_project() { + ComponentDto project = db.components().insertPrivateProject(organization); + + GroupDto userGroup = db.users().insertGroup(organization, "USERS"); + GroupDto adminGroup = db.users().insertGroup(organization, "ADMIN"); + db.users().insertProjectPermissionOnGroup(userGroup, UserRole.USER, project); + db.users().insertProjectPermissionOnGroup(adminGroup, UserRole.ADMIN, project); + + // admin with "direct" ADMIN role + UserDto admin1 = db.users().insertUser(); + db.users().insertProjectPermissionOnUser(admin1, UserRole.ADMIN, project); + + // admin2 with ADMIN role through group + UserDto admin2 = db.users().insertUser(); + db.users().insertMember(adminGroup, admin2); + + // user1 with "direct" USER role + UserDto user1 = db.users().insertUser(); + db.users().insertProjectPermissionOnUser(user1, UserRole.USER, project); + + // user2 with USER role through group + UserDto user2 = db.users().insertUser(); + db.users().insertMember(userGroup, user2); + + // user without role + UserDto userWithNoRole = db.users().insertUser(); + + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, newHashSet(userWithNoRole.getLogin()), project.uuid(), UserRole.USER)) + .isEmpty(); + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, newHashSet(user1.getLogin()), project.uuid(), UserRole.USER)) + .containsOnly(user1.getLogin()); + + Set<String> allLogins = newHashSet(admin1.getLogin(), admin2.getLogin(), user1.getLogin(), user2.getLogin(), userWithNoRole.getLogin()); + + // Admin does not have the USER permission set + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, allLogins, project.uuid(), UserRole.USER)) + .containsOnly(user1.getLogin(), user2.getLogin()); + assertThat(underTest.keepAuthorizedLoginsOnProject(dbSession, allLogins, project.uuid(), UserRole.ADMIN)) + .containsOnly(admin1.getLogin(), admin2.getLogin()); + } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java index 8b0c455d92f..84ec3a718d7 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/property/PropertiesDaoTest.java @@ -35,6 +35,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; @@ -80,79 +81,50 @@ public class PropertiesDaoTest { @Test public void shouldFindUsersForNotification() throws SQLException { - ComponentDto project1 = insertProject("uuid_45"); - ComponentDto project2 = insertProject("uuid_56"); - int userId1 = insertUser("user1"); - int userId2 = insertUser("user2"); - int userId3 = insertUser("user3"); - insertProperty("notification.NewViolations.Email", "true", project1.getId(), userId2); - insertProperty("notification.NewViolations.Twitter", "true", null, userId3); - insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), userId1); - insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), userId3); - - assertThat(underTest.selectUsersForNotification("NewViolations", "Email", null)) + ComponentDto project1 = insertPrivateProject("uuid_45"); + ComponentDto project2 = insertPrivateProject("uuid_56"); + UserDto user1 = insertUser("user1"); + UserDto user2 = insertUser("user2"); + UserDto user3 = insertUser("user3"); + insertProperty("notification.NewViolations.Email", "true", project1.getId(), user2.getId()); + insertProperty("notification.NewViolations.Twitter", "true", null, user3.getId()); + insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), user1.getId()); + insertProperty("notification.NewViolations.Twitter", "true", project1.getId(), user2.getId()); + insertProperty("notification.NewViolations.Twitter", "true", project2.getId(), user3.getId()); + dbTester.users().insertProjectPermissionOnUser(user2, UserRole.USER, project1); + dbTester.users().insertProjectPermissionOnUser(user3, UserRole.USER, project2); + dbTester.users().insertProjectPermissionOnUser(user1, UserRole.USER, project2); + + assertThat(underTest.findUsersForNotification("NewViolations", "Email", null)) .isEmpty(); - assertThat(underTest.selectUsersForNotification("NewViolations", "Email", "uuid_78")) + assertThat(underTest.findUsersForNotification("NewViolations", "Email", "uuid_78")) .isEmpty(); - assertThat(underTest.selectUsersForNotification("NewViolations", "Email", "uuid_45")) - .hasSize(1).containsOnly("user2"); - - assertThat(underTest.selectUsersForNotification("NewViolations", "Twitter", null)) - .hasSize(1) - .containsOnly("user3"); - - assertThat(underTest.selectUsersForNotification("NewViolations", "Twitter", "uuid_78")) - .isEmpty(); - - assertThat(underTest.selectUsersForNotification("NewViolations", "Twitter", "uuid_56")) - .hasSize(2) - .containsOnly("user1", "user3"); - } - - @Test - public void findNotificationSubscribers() throws SQLException { - int userId1 = insertUser("user1"); - int userId2 = insertUser("user2"); - ComponentDto projectDto = insertProject("PROJECT_A"); - long projectId = projectDto.getId(); - String projectKey = projectDto.getDbKey(); - - // global subscription - insertProperty("notification.DispatcherWithGlobalSubscribers.Email", "true", null, userId2); - // project subscription - insertProperty("notification.DispatcherWithProjectSubscribers.Email", "true", projectId, userId1); - insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", 56L, userId1); - insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", projectId, userId1); - // global subscription - insertProperty("notification.DispatcherWithGlobalAndProjectSubscribers.Email", "true", null, userId2); + assertThat(underTest.findUsersForNotification("NewViolations", "Email", project1.uuid())) + .containsOnly("user2"); - // Nobody is subscribed - assertThat(underTest.selectNotificationSubscribers("NotSexyDispatcher", "Email", projectKey)) + assertThat(underTest.findUsersForNotification("NewViolations", "Email", project2.uuid())) .isEmpty(); - // Global subscribers - assertThat(underTest.selectNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", projectKey)) - .containsOnly("user2"); + assertThat(underTest.findUsersForNotification("NewViolations", "Twitter", null)) + .containsOnly("user3"); - assertThat(underTest.selectNotificationSubscribers("DispatcherWithGlobalSubscribers", "Email", null)) - .containsOnly("user2"); + assertThat(underTest.findUsersForNotification("NewViolations", "Twitter", "uuid_78")) + .containsOnly("user3"); - // Project subscribers - assertThat(underTest.selectNotificationSubscribers("DispatcherWithProjectSubscribers", "Email", projectKey)) - .containsOnly("user1"); + assertThat(underTest.findUsersForNotification("NewViolations", "Twitter", project1.uuid())) + .containsOnly("user2", "user3"); - // Global + Project subscribers - assertThat(underTest.selectNotificationSubscribers("DispatcherWithGlobalAndProjectSubscribers", "Email", projectKey)) - .containsOnly("user1", "user2"); + assertThat(underTest.findUsersForNotification("NewViolations", "Twitter", project2.uuid())) + .containsOnly("user1", "user3"); } @Test public void hasNotificationSubscribers() throws SQLException { - int userId1 = insertUser("user1"); - int userId2 = insertUser("user2"); - Long projectId = insertProject("PROJECT_A").getId(); + int userId1 = insertUser("user1").getId(); + int userId2 = insertUser("user2").getId(); + Long projectId = insertPrivateProject("PROJECT_A").getId(); // global subscription insertProperty("notification.DispatcherWithGlobalSubscribers.Email", "true", null, userId2); // project subscription @@ -254,7 +226,7 @@ public class PropertiesDaoTest { @Test public void selectProjectProperties() throws SQLException { - ComponentDto projectDto = insertProject("A"); + ComponentDto projectDto = insertPrivateProject("A"); long projectId = projectDto.getId(); // global insertProperty("global.one", "one", null, null); @@ -279,7 +251,7 @@ public class PropertiesDaoTest { @Test @UseDataProvider("allValuesForSelect") public void selectProjectProperties_supports_all_values(String dbValue, String expected) throws SQLException { - ComponentDto projectDto = insertProject("A"); + ComponentDto projectDto = insertPrivateProject("A"); insertProperty("project.one", dbValue, projectDto.getId(), null); List<PropertyDto> dtos = underTest.selectProjectProperties(projectDto.getDbKey()); @@ -379,8 +351,8 @@ public class PropertiesDaoTest { @Test public void select_global_properties_by_keys() throws Exception { - insertProject("A"); - int userId = insertUser("B"); + insertPrivateProject("A"); + int userId = insertUser("B").getId(); String key = "key"; String anotherKey = "anotherKey"; @@ -705,9 +677,9 @@ public class PropertiesDaoTest { @Test public void delete_project_property() throws SQLException { - long projectId1 = insertProject("A").getId(); - long projectId2 = insertProject("B").getId(); - long projectId3 = insertProject("C").getId(); + long projectId1 = insertPrivateProject("A").getId(); + long projectId2 = insertPrivateProject("B").getId(); + long projectId3 = insertPrivateProject("C").getId(); long id1 = insertProperty("global.one", "one", null, null); long id2 = insertProperty("global.two", "two", null, null); long id3 = insertProperty("struts.one", "one", projectId1, null); @@ -822,7 +794,6 @@ public class PropertiesDaoTest { .hasNoResourceId() .hasUserId(100) .hasTextValue("new_user"); - } @Test @@ -1068,7 +1039,7 @@ public class PropertiesDaoTest { " and resource_id" + (resourceId == null ? " is null" : "='" + resourceId + "'")).get("id"); } - private ComponentDto insertProject(String uuid) { + private ComponentDto insertPrivateProject(String uuid) { String key = "project" + uuid; ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.getDefaultOrganization(), uuid).setDbKey(key); dbClient.componentDao().insert(session, project); @@ -1076,12 +1047,12 @@ public class PropertiesDaoTest { return project; } - private int insertUser(String login) { + private UserDto insertUser(String login) { UserDto dto = new UserDto().setLogin(login); DbSession session = dbTester.getSession(); dbClient.userDao().insert(session, dto); session.commit(); - return dto.getId(); + return dto; } private static PropertyDtoAssert assertThatDto(@Nullable PropertyDto dto) { |