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);
}
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);
}
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;
* 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();
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();
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>
<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">
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());
+ }
}
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;
@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
@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);
@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());
@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";
@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);
.hasNoResourceId()
.hasUserId(100)
.hasTextValue("new_user");
-
}
@Test
" 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);
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) {
IssueChangeNotification changeNotification = new IssueChangeNotification();
changeNotification.setRuleName(rules.getByKey(issue.ruleKey()).getName());
changeNotification.setIssue(issue);
- changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName());
+ changeNotification.setProject(project.getPublicKey(), project.getName(), getBranchName(), project.getUuid());
getComponentKey(issue).ifPresent(c -> changeNotification.setComponent(c.getPublicKey(), c.getName()));
service.deliver(changeNotification);
}
.setIssue(issue)
.setChangeAuthorLogin(context.login())
.setRuleName(rule.map(RuleDefinitionDto::getName).orElse(null))
- .setProject(project.getKey(), project.name(), project.getBranch())
+ .setProject(project)
.setComponent(component)
.setComment(comment));
return issueDto;
@Override
public void dispatch(Notification notification, Context context) {
- String projectKey = notification.getFieldValue("projectKey");
- Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findNotificationSubscribers(this, projectKey);
+ String projectUuid = notification.getFieldValue("projectUuid");
+ Multimap<String, NotificationChannel> subscribedRecipients = notificationManager.findSubscribedRecipientsForDispatcher(this, projectUuid);
// See available fields in the class IssueNotifications.
String newResolution = notification.getFieldValue("new.resolution");
if (Objects.equals(newResolution, Issue.RESOLUTION_FALSE_POSITIVE) || Objects.equals(newResolution, Issue.RESOLUTION_WONT_FIX)) {
String author = notification.getFieldValue("changeAuthor");
- String projectKey = notification.getFieldValue("projectKey");
- Multimap<String, NotificationChannel> subscribedRecipients = notifications.findNotificationSubscribers(this, projectKey);
+ String projectUuid = notification.getFieldValue("projectUuid");
+ Multimap<String, NotificationChannel> subscribedRecipients = notifications.findSubscribedRecipientsForDispatcher(this, projectUuid);
notify(author, context, subscribedRecipients);
}
}
}
public IssueChangeNotification setProject(ComponentDto project) {
- return setProject(project.getKey(), project.longName(), project.getBranch());
+ return setProject(project.getKey(), project.name(), project.getBranch(), project.uuid());
}
- public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch) {
+ public IssueChangeNotification setProject(String projectKey, String projectName, @Nullable String branch, String projectUuid) {
setFieldValue("projectName", projectName);
+ setFieldValue("projectUuid", projectUuid);
setFieldValue("projectKey", projectKey);
if (branch != null) {
setFieldValue("branch", branch);
@Override
public void dispatch(Notification notification, Context context) {
- String projectKey = notification.getFieldValue("projectKey");
+ String projectUuid = notification.getFieldValue("projectUuid");
String assignee = notification.getFieldValue("assignee");
- Multimap<String, NotificationChannel> subscribedRecipients = manager.findNotificationSubscribers(this, projectKey);
+ Multimap<String, NotificationChannel> subscribedRecipients = manager.findSubscribedRecipientsForDispatcher(this, projectUuid);
Collection<NotificationChannel> channels = subscribedRecipients.get(assignee);
for (NotificationChannel channel : channels) {
@Override
public void dispatch(Notification notification, Context context) {
- String projectKey = notification.getFieldValue("projectKey");
- Multimap<String, NotificationChannel> subscribedRecipients = manager.findNotificationSubscribers(this, projectKey);
+ String projectUuid = notification.getFieldValue("projectUuid");
+ Multimap<String, NotificationChannel> subscribedRecipients = manager.findSubscribedRecipientsForDispatcher(this, projectUuid);
for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
String userLogin = channelsByRecipients.getKey();
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import java.io.IOException;
import java.io.InvalidClassException;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
-import javax.annotation.Nullable;
+import java.util.Set;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
-import org.sonar.db.notification.NotificationQueueDao;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
import org.sonar.db.notification.NotificationQueueDto;
-import org.sonar.db.property.PropertiesDao;
import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
public class DefaultNotificationManager implements NotificationManager {
private static final String UNABLE_TO_READ_NOTIFICATION = "Unable to read notification";
private NotificationChannel[] notificationChannels;
- private NotificationQueueDao notificationQueueDao;
- private PropertiesDao propertiesDao;
+ private final DbClient dbClient;
private boolean alreadyLoggedDeserializationIssue = false;
/**
* Default constructor used by Pico
*/
- public DefaultNotificationManager(NotificationChannel[] channels, NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
+ public DefaultNotificationManager(NotificationChannel[] channels,
+ DbClient dbClient) {
this.notificationChannels = channels;
- this.notificationQueueDao = notificationQueueDao;
- this.propertiesDao = propertiesDao;
- }
-
- /**
- * Constructor if no notification channel
- */
- public DefaultNotificationManager(NotificationQueueDao notificationQueueDao, PropertiesDao propertiesDao) {
- this(new NotificationChannel[0], notificationQueueDao, propertiesDao);
+ this.dbClient = dbClient;
}
/**
@Override
public void scheduleForSending(Notification notification) {
NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
- notificationQueueDao.insert(singletonList(dto));
+ dbClient.notificationQueueDao().insert(singletonList(dto));
}
/**
* Give the notification queue so that it can be processed
*/
public Notification getFromQueue() {
int batchSize = 1;
- List<NotificationQueueDto> notificationDtos = notificationQueueDao.selectOldest(batchSize);
+ List<NotificationQueueDto> notificationDtos = dbClient.notificationQueueDao().selectOldest(batchSize);
if (notificationDtos.isEmpty()) {
return null;
}
- notificationQueueDao.delete(notificationDtos);
+ dbClient.notificationQueueDao().delete(notificationDtos);
return convertToNotification(notificationDtos);
}
}
public long count() {
- return notificationQueueDao.count();
+ return dbClient.notificationQueueDao().count();
}
/**
* {@inheritDoc}
*/
@Override
- public Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher,
- @Nullable String projectUuid) {
+ public Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, String projectUuid) {
+ requireNonNull(projectUuid, "ProjectUUID is mandatory");
String dispatcherKey = dispatcher.getKey();
SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
String channelKey = channel.getKey();
// Find users subscribed globally to the dispatcher (i.e. not on a specific project)
- addUsersToRecipientListForChannel(propertiesDao.selectUsersForNotification(dispatcherKey, channelKey, null), recipients, channel);
-
- if (projectUuid != null) {
- // Find users subscribed to the dispatcher specifically for the project
- addUsersToRecipientListForChannel(propertiesDao.selectUsersForNotification(dispatcherKey, channelKey, projectUuid), recipients, channel);
+ // And users subscribed to the dispatcher specifically for the project
+ Set<String> subscribedUsers = dbClient.propertiesDao().findUsersForNotification(dispatcherKey, channelKey, projectUuid);
+
+ if (!subscribedUsers.isEmpty()) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Set<String> filteredSubscribedUsers = dbClient.authorizationDao().keepAuthorizedLoginsOnProject(dbSession, subscribedUsers, projectUuid, UserRole.USER);
+ addUsersToRecipientListForChannel(filteredSubscribedUsers, recipients, channel);
+ }
}
}
return recipients;
}
- @Override
- public Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey) {
- String dispatcherKey = dispatcher.getKey();
-
- SetMultimap<String, NotificationChannel> recipients = HashMultimap.create();
- for (NotificationChannel channel : notificationChannels) {
- addUsersToRecipientListForChannel(propertiesDao.selectNotificationSubscribers(dispatcherKey, channel.getKey(), componentKey), recipients, channel);
- }
-
- return recipients;
- }
-
@VisibleForTesting
protected List<NotificationChannel> getChannels() {
return Arrays.asList(notificationChannels);
}
- private static void addUsersToRecipientListForChannel(List<String> users, SetMultimap<String, NotificationChannel> recipients, NotificationChannel channel) {
+ private static void addUsersToRecipientListForChannel(Collection<String> users, SetMultimap<String, NotificationChannel> recipients, NotificationChannel channel) {
for (String username : users) {
recipients.put(username, channel);
}
}
-
}
package org.sonar.server.notification;
import com.google.common.collect.Multimap;
-import javax.annotation.Nullable;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
* @return the list of user login along with the subscribed channels
*/
Multimap<String, NotificationChannel> findSubscribedRecipientsForDispatcher(NotificationDispatcher dispatcher, String projectUuid);
-
- Multimap<String, NotificationChannel> findNotificationSubscribers(NotificationDispatcher dispatcher, @Nullable String componentKey);
}
assertThat(issueChangeNotification.getFieldValue("componentKey")).isEqualTo(file.getDbKey());
assertThat(issueChangeNotification.getFieldValue("componentName")).isEqualTo(file.longName());
assertThat(issueChangeNotification.getFieldValue("projectKey")).isEqualTo(project.getDbKey());
+ assertThat(issueChangeNotification.getFieldValue("projectUuid")).isEqualTo(project.uuid());
assertThat(issueChangeNotification.getFieldValue("projectName")).isEqualTo(project.name());
assertThat(issueChangeNotification.getFieldValue("ruleName")).isEqualTo(rule.getName());
assertThat(issueChangeNotification.getFieldValue("changeAuthor")).isEqualTo("john");
recipients.put("simon", emailChannel);
recipients.put("freddy", twitterChannel);
recipients.put("godin", twitterChannel);
- when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+ when(notifications.findSubscribedRecipientsForDispatcher(dispatcher, "uuid1")).thenReturn(recipients);
- Notification notification = new IssueChangeNotification().setFieldValue("projectKey", "struts")
+ Notification notification = new IssueChangeNotification()
+ .setFieldValue("projectKey", "struts")
+ .setFieldValue("projectUuid", "uuid1")
.setFieldValue("changeAuthor", "olivier")
.setFieldValue("assignee", "freddy");
dispatcher.performDispatch(notification, context);
recipients.put("simon", emailChannel);
recipients.put("freddy", twitterChannel);
recipients.put("godin", twitterChannel);
- when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+ when(notifications.findSubscribedRecipientsForDispatcher(dispatcher, "uuid1")).thenReturn(recipients);
// change author is the assignee
- dispatcher.performDispatch(new IssueChangeNotification().setFieldValue("projectKey", "struts")
- .setFieldValue("changeAuthor", "simon").setFieldValue("assignee", "simon"), context);
+ dispatcher.performDispatch(
+ new IssueChangeNotification()
+ .setFieldValue("projectKey", "struts")
+ .setFieldValue("projectUuid", "uuid1")
+ .setFieldValue("changeAuthor", "simon")
+ .setFieldValue("assignee", "simon"), context);
// no change author
dispatcher.performDispatch(new IssueChangeNotification().setFieldValue("projectKey", "struts")
recipients.put("simon", emailChannel);
recipients.put("freddy", twitterChannel);
recipients.put("godin", twitterChannel);
- when(notifications.findNotificationSubscribers(underTest, "struts")).thenReturn(recipients);
+ when(notifications.findSubscribedRecipientsForDispatcher(underTest, "uuid1")).thenReturn(recipients);
Notification fpNotif = new IssueChangeNotification().setFieldValue("projectKey", "struts")
+ .setFieldValue("projectUuid", "uuid1")
.setFieldValue("changeAuthor", "godin")
.setFieldValue("new.resolution", Issue.RESOLUTION_FALSE_POSITIVE)
.setFieldValue("assignee", "freddy");
*/
@Test
public void ignore_other_resolutions() {
- Multimap<String, NotificationChannel> recipients = HashMultimap.create();
- recipients.put("simon", emailChannel);
- recipients.put("freddy", twitterChannel);
- when(notifications.findNotificationSubscribers(underTest, "struts")).thenReturn(recipients);
-
Notification fixedNotif = new IssueChangeNotification().setFieldValue("projectKey", "struts")
.setFieldValue("changeAuthor", "godin")
.setFieldValue("new.resolution", Issue.RESOLUTION_FIXED)
@Test
public void set_project_without_branch() {
- IssueChangeNotification result = notification.setProject("MyService", "My Service", null);
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", null, "uuid1");
assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
+ assertThat(result.getFieldValue("projectUuid")).isEqualTo("uuid1");
assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
assertThat(result.getFieldValue("branch")).isNull();
}
@Test
public void set_project_with_branch() {
- IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1");
+ IssueChangeNotification result = notification.setProject("MyService", "My Service", "feature1", "uuid2");
assertThat(result.getFieldValue("projectKey")).isEqualTo("MyService");
+ assertThat(result.getFieldValue("projectUuid")).isEqualTo("uuid2");
assertThat(result.getFieldValue("projectName")).isEqualTo("My Service");
assertThat(result.getFieldValue("branch")).isEqualTo("feature1");
}
Notification notification = new IssueChangeNotification()
.setChangeAuthorLogin("simon")
- .setProject("Struts", "org.apache:struts", null);
+ .setProject("Struts", "org.apache:struts", null, "");
EmailMessage message = underTest.format(notification);
assertThat(message.getFrom()).isEqualTo("Simon");
Notification notification = new IssueChangeNotification()
.setChangeAuthorLogin("simon")
- .setProject("Struts", "org.apache:struts", null);
+ .setProject("Struts", "org.apache:struts", null, "");
EmailMessage message = underTest.format(notification);
assertThat(message.getFrom()).isEqualTo("simon");
Multimap<String, NotificationChannel> recipients = HashMultimap.create();
recipients.put("user1", emailChannel);
recipients.put("user2", twitterChannel);
- when(notificationManager.findNotificationSubscribers(underTest, "struts")).thenReturn(recipients);
+ when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, "uuid1")).thenReturn(recipients);
Notification notification = new Notification(MyNewIssuesNotification.MY_NEW_ISSUES_NOTIF_TYPE)
.setFieldValue("projectKey", "struts")
+ .setFieldValue("projectUuid", "uuid1")
.setFieldValue("assignee", "user1");
underTest.performDispatch(notification, context);
Multimap<String, NotificationChannel> recipients = HashMultimap.create();
recipients.put("user1", emailChannel);
recipients.put("user2", twitterChannel);
- when(notifications.findNotificationSubscribers(dispatcher, "struts")).thenReturn(recipients);
+ when(notifications.findSubscribedRecipientsForDispatcher(dispatcher, "uuid1")).thenReturn(recipients);
- Notification notification = new Notification(NewIssuesNotification.TYPE).setFieldValue("projectKey", "struts");
+ Notification notification = new Notification(NewIssuesNotification.TYPE)
+ .setFieldValue("projectKey", "struts")
+ .setFieldValue("projectUuid", "uuid1");
dispatcher.performDispatch(notification, context);
verify(context).addUser("user1", emailChannel);
verify(notificationManager).scheduleForSending(issueChangeNotificationCaptor.capture());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("key")).isEqualTo(issueDto.getKey());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("componentName")).isEqualTo(file.longName());
- assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectName")).isEqualTo(project.longName());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectName")).isEqualTo(project.name());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectKey")).isEqualTo(project.getDbKey());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("ruleName")).isEqualTo(rule.getName());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
verify(notificationManager).scheduleForSending(issueChangeNotificationCaptor.capture());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("key")).isEqualTo(issueDto.getKey());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("componentName")).isEqualTo(fileOnBranch.longName());
- assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectName")).isEqualTo(project.longName());
+ assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectName")).isEqualTo(project.name());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("projectKey")).isEqualTo(project.getDbKey());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("ruleName")).isEqualTo(rule.getName());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
*/
package org.sonar.server.notification;
-import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import java.io.InvalidClassException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.sonar.api.notifications.Notification;
import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
import org.sonar.db.notification.NotificationQueueDao;
import org.sonar.db.notification.NotificationQueueDto;
+import org.sonar.db.permission.AuthorizationDao;
import org.sonar.db.property.PropertiesDao;
+import static com.google.common.collect.Sets.newHashSet;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
public class DefaultNotificationManagerTest {
- private DefaultNotificationManager manager;
+ private DefaultNotificationManager underTest;
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
@Mock
private PropertiesDao propertiesDao;
@Mock
private NotificationQueueDao notificationQueueDao;
+ @Mock
+ private AuthorizationDao authorizationDao;
+
+ @Mock
+ private DbClient dbClient;
+
+ @Captor
+ private ArgumentCaptor<List<String>> captorLogins;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(dispatcher.getKey()).thenReturn("NewViolations");
when(emailChannel.getKey()).thenReturn("Email");
when(twitterChannel.getKey()).thenReturn("Twitter");
+ when(dbClient.propertiesDao()).thenReturn(propertiesDao);
+ when(dbClient.notificationQueueDao()).thenReturn(notificationQueueDao);
+ when(dbClient.authorizationDao()).thenReturn(authorizationDao);
- manager = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, notificationQueueDao, propertiesDao);
+ underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient);
}
@Test
public void shouldProvideChannelList() {
- assertThat(manager.getChannels()).containsOnly(emailChannel, twitterChannel);
+ assertThat(underTest.getChannels()).containsOnly(emailChannel, twitterChannel);
- manager = new DefaultNotificationManager(notificationQueueDao, propertiesDao);
- assertThat(manager.getChannels()).hasSize(0);
+ underTest = new DefaultNotificationManager(new NotificationChannel[] {} , db.getDbClient());
+ assertThat(underTest.getChannels()).hasSize(0);
}
@Test
public void shouldPersist() {
Notification notification = new Notification("test");
- manager.scheduleForSending(notification);
+ underTest.scheduleForSending(notification);
verify(notificationQueueDao, only()).insert(any(List.class));
}
List<NotificationQueueDto> dtos = Arrays.asList(dto);
when(notificationQueueDao.selectOldest(1)).thenReturn(dtos);
- assertThat(manager.getFromQueue()).isNotNull();
+ assertThat(underTest.getFromQueue()).isNotNull();
InOrder inOrder = inOrder(notificationQueueDao);
inOrder.verify(notificationQueueDao).selectOldest(1);
inOrder.verify(notificationQueueDao).delete(dtos);
}
+
// SONAR-4739
@Test
public void shouldNotFailWhenUnableToDeserialize() throws Exception {
List<NotificationQueueDto> dtos = Arrays.asList(dto1);
when(notificationQueueDao.selectOldest(1)).thenReturn(dtos);
- manager = spy(manager);
- assertThat(manager.getFromQueue()).isNull();
- assertThat(manager.getFromQueue()).isNull();
+ underTest = spy(underTest);
+ assertThat(underTest.getFromQueue()).isNull();
+ assertThat(underTest.getFromQueue()).isNull();
- verify(manager, times(1)).logDeserializationIssue();
+ verify(underTest, times(1)).logDeserializationIssue();
}
@Test
public void shouldFindNoRecipient() {
- assertThat(manager.findSubscribedRecipientsForDispatcher(dispatcher, "uuid_45").asMap().entrySet()).hasSize(0);
+ assertThat(underTest.findSubscribedRecipientsForDispatcher(dispatcher, "uuid_45").asMap().entrySet()).hasSize(0);
}
@Test
public void shouldFindSubscribedRecipientForGivenResource() {
- when(propertiesDao.selectUsersForNotification("NewViolations", "Email", "uuid_45")).thenReturn(Lists.newArrayList("user1", "user2"));
- when(propertiesDao.selectUsersForNotification("NewViolations", "Email", null)).thenReturn(Lists.newArrayList("user1", "user3"));
- when(propertiesDao.selectUsersForNotification("NewViolations", "Twitter", "uuid_56")).thenReturn(Lists.newArrayList("user2"));
- when(propertiesDao.selectUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3"));
- when(propertiesDao.selectUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4"));
-
- Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, "uuid_45");
- assertThat(multiMap.entries()).hasSize(4);
-
- Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
- assertThat(map.get("user1")).containsOnly(emailChannel);
- assertThat(map.get("user2")).containsOnly(emailChannel);
- assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
- assertThat(map.get("user4")).isNull();
- }
-
- @Test
- public void shouldFindSubscribedRecipientForNoResource() {
- when(propertiesDao.selectUsersForNotification("NewViolations", "Email", "uuid_45")).thenReturn(Lists.newArrayList("user1", "user2"));
- when(propertiesDao.selectUsersForNotification("NewViolations", "Email", null)).thenReturn(Lists.newArrayList("user1", "user3"));
- when(propertiesDao.selectUsersForNotification("NewViolations", "Twitter", "uuid_56")).thenReturn(Lists.newArrayList("user2"));
- when(propertiesDao.selectUsersForNotification("NewViolations", "Twitter", null)).thenReturn(Lists.newArrayList("user3"));
- when(propertiesDao.selectUsersForNotification("NewAlerts", "Twitter", null)).thenReturn(Lists.newArrayList("user4"));
-
- Multimap<String, NotificationChannel> multiMap = manager.findSubscribedRecipientsForDispatcher(dispatcher, null);
+ when(propertiesDao.findUsersForNotification("NewViolations", "Email", "uuid_45"))
+ .thenReturn(newHashSet("user1", "user3"));
+ when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", "uuid_56"))
+ .thenReturn(newHashSet("user2"));
+ when(propertiesDao.findUsersForNotification("NewViolations", "Twitter", "uuid_45"))
+ .thenReturn(newHashSet("user3"));
+ when(propertiesDao.findUsersForNotification("NewAlerts", "Twitter", "uuid_45"))
+ .thenReturn(newHashSet("user4"));
+
+ when(authorizationDao.keepAuthorizedLoginsOnProject(any(), eq(newHashSet("user1", "user3")), eq("uuid_45"), eq("user")))
+ .thenReturn(newHashSet("user1", "user3"));
+ when(authorizationDao.keepAuthorizedLoginsOnProject(any(), eq(newHashSet("user3")), eq("uuid_45"), eq("user")))
+ .thenReturn(newHashSet("user3"));
+
+ Multimap<String, NotificationChannel> multiMap = underTest.findSubscribedRecipientsForDispatcher(dispatcher, "uuid_45");
assertThat(multiMap.entries()).hasSize(3);
Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
assertThat(map.get("user1")).containsOnly(emailChannel);
- assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
assertThat(map.get("user2")).isNull();
+ assertThat(map.get("user3")).containsOnly(emailChannel, twitterChannel);
assertThat(map.get("user4")).isNull();
}
-
- @Test
- public void findNotificationSubscribers() {
- when(propertiesDao.selectNotificationSubscribers("NewViolations", "Email", "struts")).thenReturn(Lists.newArrayList("user1", "user2"));
- when(propertiesDao.selectNotificationSubscribers("NewViolations", "Twitter", "struts")).thenReturn(Lists.newArrayList("user2"));
-
- Multimap<String, NotificationChannel> multiMap = manager.findNotificationSubscribers(dispatcher, "struts");
- assertThat(multiMap.entries()).hasSize(3);
-
- Map<String, Collection<NotificationChannel>> map = multiMap.asMap();
- assertThat(map.get("user1")).containsOnly(emailChannel);
- assertThat(map.get("user2")).containsOnly(emailChannel, twitterChannel);
- assertThat(map.get("other")).isNull();
- }
}
String randomKey = RandomStringUtils.randomAlphabetic(3);
String randomNumberOfWhitespaces = StringUtils.repeat(" ", 1 + new Random().nextInt(10));
- assertThat(t.apply(underTest.put(randomKey, randomNumberOfWhitespaces + String.valueOf(value)), randomKey)).contains(value);
- assertThat(t.apply(underTest.put(randomKey, String.valueOf(value) + randomNumberOfWhitespaces), randomKey)).contains(value);
- assertThat(t.apply(underTest.put(randomKey, randomNumberOfWhitespaces + String.valueOf(value) + randomNumberOfWhitespaces), randomKey)).contains(value);
+ assertThat(t.apply(underTest.put(randomKey, randomNumberOfWhitespaces + String.valueOf(value)), randomKey)).isEqualTo(Optional.of(value));
+ assertThat(t.apply(underTest.put(randomKey, String.valueOf(value) + randomNumberOfWhitespaces), randomKey)).isEqualTo(Optional.of(value));
+ assertThat(t.apply(underTest.put(randomKey, randomNumberOfWhitespaces + String.valueOf(value) + randomNumberOfWhitespaces), randomKey)).isEqualTo(Optional.of(value));
}
private static class DumpMapConfiguration implements Configuration {