diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java b/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java index 66f868c50f..a88617ef8d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/config/TaskConfig.java @@ -43,6 +43,7 @@ import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import it.infn.mw.iam.core.web.group.GroupRequestReminderTask; @Configuration @EnableScheduling @@ -67,6 +68,7 @@ public class TaskConfig implements SchedulingConfigurer { private AupReminderTask aupReminderTask; private ExecutorService taskScheduler; private GarbageCollector garbageCollector; + private GroupRequestReminderTask groupRequestReminderTask; @Value("${notification.disable}") boolean notificationDisabled; @@ -77,7 +79,8 @@ public class TaskConfig implements SchedulingConfigurer { public TaskConfig(NotificationStoreService notificationStoreService, NotificationDeliveryTask deliveryTask, LifecycleProperties lifecycleProperties, ExpiredAccountsHandler expiredAccountsHandler, AupReminderTask aupReminderTask, - ExecutorService taskScheduler, GarbageCollector garbageCollector) { + ExecutorService taskScheduler, GarbageCollector garbageCollector, GroupRequestReminderTask groupRequestReminderTask + ) { this.notificationStoreService = notificationStoreService; this.deliveryTask = deliveryTask; @@ -86,6 +89,8 @@ public TaskConfig(NotificationStoreService notificationStoreService, this.aupReminderTask = aupReminderTask; this.taskScheduler = taskScheduler; this.garbageCollector = garbageCollector; + this.groupRequestReminderTask = groupRequestReminderTask; + } @Scheduled(fixedRateString = "${task.wellKnownCacheCleanupPeriodSecs:300}", @@ -130,6 +135,13 @@ public void scheduledAupRemindersTask() { aupReminderTask.sendAupReminders(); } + @Scheduled(fixedRateString = "${task.groupRequestReminderPeriodSecs:86400}", + timeUnit = TimeUnit.SECONDS, initialDelay = TEN_MINUTES_MSEC) + public void scheduledGroupRequestRemindersTask() { + + groupRequestReminderTask.sendReminders(); + } + public void schedulePendingNotificationsDelivery(final ScheduledTaskRegistrar taskRegistrar) { if (notificationTaskPeriodMsec < 0) { diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/core/web/group/GroupRequestReminderTask.java b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/group/GroupRequestReminderTask.java new file mode 100644 index 0000000000..f52a11a68b --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/core/web/group/GroupRequestReminderTask.java @@ -0,0 +1,148 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.infn.mw.iam.core.web.group; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import it.infn.mw.iam.core.IamGroupRequestStatus; +import it.infn.mw.iam.notification.NotificationFactory; +import it.infn.mw.iam.persistence.model.IamGroup; +import it.infn.mw.iam.persistence.model.IamGroupRequest; +import it.infn.mw.iam.persistence.repository.IamEmailNotificationRepository; +import it.infn.mw.iam.persistence.repository.IamGroupRequestRepository; +import it.infn.mw.iam.notification.service.resolver.AddressResolutionService; +import it.infn.mw.iam.notification.service.resolver.AdminNotificationDeliveryStrategy; + +@Component +public class GroupRequestReminderTask { + + private static final Logger LOG = LoggerFactory.getLogger(GroupRequestReminderTask.class); + + private static final String GM_AUDIENCE_PREFIX = "gm:"; + + @Value("${group-request-reminder.enabled:false}") + private boolean enabled; + + @Value("${group-request-reminder.days:5}") + private int windowDays; + + @Value("${group-request-reminder.repeat-interval-days:2}") + private int repeatIntervalDays; + + @Value("${group-request-reminder.notify-admins:false}") + private boolean notifyAdmins; + + private final IamGroupRequestRepository groupRequestRepo; + private final NotificationFactory notificationFactory; + private final IamEmailNotificationRepository emailNotificationRepo; + private final AddressResolutionService addressResolutionService; + private final AdminNotificationDeliveryStrategy adminDeliveryStrategy; + + public GroupRequestReminderTask( + IamGroupRequestRepository groupRequestRepo, + NotificationFactory notificationFactory, + IamEmailNotificationRepository emailNotificationRepo, + AddressResolutionService addressResolutionService, + AdminNotificationDeliveryStrategy adminDeliveryStrategy + ) { + this.groupRequestRepo = groupRequestRepo; + this.notificationFactory = notificationFactory; + this.emailNotificationRepo = emailNotificationRepo; + this.addressResolutionService = addressResolutionService; + this.adminDeliveryStrategy = adminDeliveryStrategy; + } + + public void sendReminders() { + + if (!enabled) { + return; + } + + Date cutoffDate = toDate(LocalDate.now().minusDays(windowDays)); + Date sinceDate = toDate(LocalDate.now().minusDays(repeatIntervalDays)); + + List pendingRequests = + groupRequestRepo.findPendingRequestsOlderThan(IamGroupRequestStatus.PENDING, cutoffDate); + + if (pendingRequests.isEmpty()) { + LOG.debug("No pending group requests older than {} days", windowDays); + return; + } + + Map> requestsByGroup = pendingRequests.stream() + .collect(Collectors.groupingBy(IamGroupRequest::getGroup)); + + LOG.info("Found {} pending group request(s) across {} group(s) older than {} days", + pendingRequests.size(), requestsByGroup.size(), windowDays); + + for (Map.Entry> entry : requestsByGroup.entrySet()) { + IamGroup group = entry.getKey(); + List requests = entry.getValue(); + + String subjectPattern = "%group " + group.getName() + "%"; + + if (emailNotificationRepo.countGroupMembershipReminders(subjectPattern, sinceDate) > 0) { + LOG.debug("Reminder already sent within last {} day(s) for group {}", repeatIntervalDays, + group.getName()); + continue; + } + + List recipients = resolveRecipients(group); + + if (recipients.isEmpty()) { + LOG.warn("No recipients found for group {} reminder, skipping", group.getName()); + continue; + } + + notificationFactory.createGroupMembershipReminderMessage(group, requests, recipients); + LOG.info("Sent reminder for group {} with {} pending request(s) to {} recipient(s)", + group.getName(), requests.size(), recipients.size()); + } + } + + private List resolveRecipients(IamGroup group) { + List gmAddresses = addressResolutionService + .resolveAddressesForAudience(GM_AUDIENCE_PREFIX + group.getUuid()); + + if (gmAddresses.isEmpty()) { + LOG.debug("No group managers for the group {}, falling back to admins", group.getName()); + return adminDeliveryStrategy.resolveAdminEmailAddresses(); + } + + if (notifyAdmins) { + List combinedAddress = new ArrayList<>(gmAddresses); + combinedAddress.addAll(adminDeliveryStrategy.resolveAdminEmailAddresses()); + return combinedAddress; + } + + return gmAddresses; + } + + private Date toDate(LocalDate localDate) { + return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); + } +} diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java index 15ef80a63b..e0fb123590 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/notification/NotificationFactory.java @@ -26,6 +26,7 @@ import it.infn.mw.iam.persistence.model.IamGroupRequest; import it.infn.mw.iam.persistence.model.IamRegistrationRequest; import it.infn.mw.iam.persistence.model.IamX509Certificate; +import it.infn.mw.iam.persistence.model.IamGroup; public interface NotificationFactory { @@ -46,6 +47,9 @@ IamEmailNotification createRequestRejectedMessage(IamRegistrationRequest request IamEmailNotification createGroupMembershipRejectedMessage(IamGroupRequest groupRequest); + IamEmailNotification createGroupMembershipReminderMessage(IamGroup group, + List pendingRequests, List recipients); + IamEmailNotification createClientStatusChangedMessageFor(ClientDetailsEntity client, List accounts); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java b/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java index ecff9e24ca..bc143037ea 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/notification/TransientNotificationFactory.java @@ -18,10 +18,12 @@ import static java.util.Arrays.asList; import java.io.IOException; +import java.text.SimpleDateFormat; import java.time.Clock; import java.time.LocalDate; import java.time.ZoneId; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -54,6 +56,7 @@ import it.infn.mw.iam.persistence.model.IamNotificationReceiver; import it.infn.mw.iam.persistence.model.IamRegistrationRequest; import it.infn.mw.iam.persistence.model.IamX509Certificate; +import it.infn.mw.iam.persistence.model.IamGroup; public class TransientNotificationFactory implements NotificationFactory { @@ -65,6 +68,9 @@ public class TransientNotificationFactory implements NotificationFactory { private static final String MOTIVATION_FIELD = "motivation"; private static final String AUP_PATH = "%s/iam/aup/sign"; private static final String AUP_URL = "aupUrl"; + private static final String NOTES_FIELD = "notes"; + private static final String INDIGO_DASHBOARD_URL_FIELD = "indigoDashboardUrl"; + private static final String INDIGO_DASHBOARD_PATH = "%s/dashboard#!/requests"; @Value("${iam.baseUrl}") private String baseUrl; @@ -162,9 +168,9 @@ public IamEmailNotification createAdminHandleRequestMessage(IamRegistrationReque model.put("name", name); model.put(USERNAME_FIELD, username); model.put("email", email); - model.put("indigoDashboardUrl", String.format("%s/dashboard#!/requests", baseUrl)); + model.put(INDIGO_DASHBOARD_URL_FIELD, String.format(INDIGO_DASHBOARD_PATH, baseUrl)); model.put(ORGANISATION_NAME, organisationName); - model.put("notes", request.getNotes()); + model.put(NOTES_FIELD, request.getNotes()); return createMessage("adminHandleRequest.ftl", model, IamNotificationType.CONFIRMATION, properties.getSubject().get("adminHandleRequest"), @@ -202,8 +208,8 @@ public IamEmailNotification createAdminHandleGroupRequestMessage(IamGroupRequest model.put("name", groupRequest.getAccount().getUserInfo().getName()); model.put(USERNAME_FIELD, groupRequest.getAccount().getUsername()); model.put(GROUPNAME_FIELD, groupName); - model.put("notes", groupRequest.getNotes()); - model.put("indigoDashboardUrl", String.format("%s/dashboard#!/requests", baseUrl)); + model.put(NOTES_FIELD, groupRequest.getNotes()); + model.put(INDIGO_DASHBOARD_URL_FIELD, String.format(INDIGO_DASHBOARD_PATH, baseUrl)); model.put(ORGANISATION_NAME, organisationName); String subject = String.format("New membership request for group %s", groupName); @@ -261,6 +267,38 @@ public IamEmailNotification createGroupMembershipRejectedMessage(IamGroupRequest return notification; } + @Override + public IamEmailNotification createGroupMembershipReminderMessage(IamGroup group, + List pendingRequests, List recipients) { + String groupName = group.getName(); + SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyyy"); + + List> pendingUsers = new ArrayList<>(); + for (IamGroupRequest request : pendingRequests) { + Map entry = new HashMap<>(); + entry.put("name", request.getAccount().getUserInfo().getName()); + entry.put(USERNAME_FIELD, request.getAccount().getUsername()); + entry.put(NOTES_FIELD, request.getNotes()); + entry.put("requestDate", dateFormat.format(request.getCreationTime())); + pendingUsers.add(entry); + } + + Map model = new HashMap<>(); + model.put(GROUPNAME_FIELD, groupName); + model.put("pendingUsers", pendingUsers); + model.put("requestCount", pendingRequests.size()); + model.put(INDIGO_DASHBOARD_URL_FIELD, String.format(INDIGO_DASHBOARD_PATH, baseUrl)); + model.put(ORGANISATION_NAME, organisationName); + + String subject = String.format("Reminder: %d pending membership request(s) for group %s", + pendingRequests.size(), groupName); + + LOG.debug("Create group membership reminder for group {} with {} pending request(s)", + groupName, pendingRequests.size()); + return createMessage("groupMembershipReminder.ftl", model, + IamNotificationType.GROUP_MEMBERSHIP_REMINDER, subject, recipients); + } + @Override public IamEmailNotification createClientStatusChangedMessageFor(ClientDetailsEntity client, List accounts) { diff --git a/iam-login-service/src/main/resources/application.yml b/iam-login-service/src/main/resources/application.yml index 924ae10ff3..2ba71e3b58 100644 --- a/iam-login-service/src/main/resources/application.yml +++ b/iam-login-service/src/main/resources/application.yml @@ -224,6 +224,13 @@ task: approvalCleanupPeriodMsec: ${IAM_APPROVAL_CLEANUP_PERIOD_MSEC:300000} deviceCodeCleanupPeriodMsec: ${IAM_DEVICE_CODE_CLEANUP_PERIOD_MSEC:300000} wellKnownCacheCleanupPeriodSecs: ${IAM_WELL_KNOWN_CACHE_CLEANUP_PERIOD_SECS:300} + groupRequestReminderPeriodSecs: ${IAM_GROUP_REQUEST_REMINDER_PERIOD_SECS:86400} + +group-request-reminder: + enabled: ${IAM_GROUP_REQUEST_REMINDER_ENABLED:false} + days: ${IAM_GROUP_REQUEST_REMINDER_DAYS:5} + repeat-interval-days: ${IAM_GROUP_REQUEST_REMINDER_REPEAT_INTERVAL_DAYS:2} + notify-admins: ${IAM_GROUP_REQUEST_REMINDER_NOTIFY_ADMINS:false} client-registration: allow-for: ${IAM_CLIENT_REGISTRATION_ALLOW_FOR:ANYONE} diff --git a/iam-login-service/src/main/resources/email-templates/groupMembershipReminder.ftl b/iam-login-service/src/main/resources/email-templates/groupMembershipReminder.ftl new file mode 100644 index 0000000000..619ca4666b --- /dev/null +++ b/iam-login-service/src/main/resources/email-templates/groupMembershipReminder.ftl @@ -0,0 +1,17 @@ +Dear Group Manager, + +there are ${requestCount} pending membership request(s) for group ${groupName} awaiting your action: + +<#list pendingUsers as user> + Name: ${user.name} + Username: ${user.username} + Requested on: ${user.requestDate} + Notes: ${user.notes} + + +You can approve or reject these requests by following the link below: + +${indigoDashboardUrl} + + +The ${organisationName} registration service diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/web/group/GroupRequestReminderTaskTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/web/group/GroupRequestReminderTaskTests.java new file mode 100644 index 0000000000..e1955fce11 --- /dev/null +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/web/group/GroupRequestReminderTaskTests.java @@ -0,0 +1,238 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package it.infn.mw.iam.test.core.web.group; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.hasSize; + +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.test.context.TestPropertySource; + +import it.infn.mw.iam.IamLoginService; +import it.infn.mw.iam.core.IamGroupRequestStatus; +import it.infn.mw.iam.core.IamNotificationType; +import it.infn.mw.iam.core.web.group.GroupRequestReminderTask; +import it.infn.mw.iam.persistence.model.IamAccount; +import it.infn.mw.iam.persistence.model.IamAuthority; +import it.infn.mw.iam.persistence.model.IamEmailNotification; +import it.infn.mw.iam.persistence.model.IamGroup; +import it.infn.mw.iam.persistence.model.IamGroupRequest; +import it.infn.mw.iam.persistence.repository.IamAccountRepository; +import it.infn.mw.iam.persistence.repository.IamAuthoritiesRepository; +import it.infn.mw.iam.persistence.repository.IamEmailNotificationRepository; +import it.infn.mw.iam.persistence.repository.IamGroupRepository; +import it.infn.mw.iam.persistence.repository.IamGroupRequestRepository; +import it.infn.mw.iam.test.util.WithAnonymousUser; +import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest; + +@IamMockMvcIntegrationTest +@SpringBootTest(classes = {IamLoginService.class}, webEnvironment = WebEnvironment.MOCK) +@WithAnonymousUser +@TestPropertySource(properties = { + "group-request-reminder.enabled=true", + "group-request-reminder.days=1", + "group-request-reminder.repeat-interval-days=1", + "group-request-reminder.notify-admins=false", + "notification.disable=false" +}) +class GroupRequestReminderTaskTests { + + private static final String GROUP_NAME = "Test-001"; + + @Autowired + private GroupRequestReminderTask reminderTask; + + @Autowired + private IamGroupRequestRepository groupRequestRepo; + + @Autowired + private IamGroupRepository groupRepo; + + @Autowired + private IamAccountRepository accountRepo; + + @Autowired + private IamAuthoritiesRepository authoritiesRepo; + + @Autowired + private IamEmailNotificationRepository emailRepo; + + @BeforeEach + void setup() { + emailRepo.deleteAll(); + groupRequestRepo.deleteAll(); + } + + @Test + void reminderIsCreatedForOldPendingRequest() { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + IamAccount account = accountRepo.findByUsername("test_100").orElseThrow(); + assignGroupManager(account, group); + savePendingRequest(account, group, 5); + + reminderTask.sendReminders(); + + List reminders = getReminders(); + assertThat(reminders, hasSize(1)); + assertThat(reminders.get(0).getBody(), containsString(GROUP_NAME)); + assertThat(reminders.get(0).getBody(), containsString("test_100")); + assertThat(reminders.get(0).getBody(), containsString("Dear Group Manager")); + } + + @Test + void reminderIsNotDuplicated() { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + IamAccount account = accountRepo.findByUsername("test_100").orElseThrow(); + assignGroupManager(account, group); + savePendingRequest(account, group, 5); + + reminderTask.sendReminders(); + reminderTask.sendReminders(); + reminderTask.sendReminders(); + + assertThat(getReminders(), hasSize(1)); + } + + @Test + void noReminderForRecentRequest() { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + IamAccount account = accountRepo.findByUsername("test_100").orElseThrow(); + assignGroupManager(account, group); + savePendingRequest(account, group, 0); + + reminderTask.sendReminders(); + + assertThat(getReminders(), hasSize(0)); + } + + @Test + void noReminderForApprovedRequest() { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + IamAccount account = accountRepo.findByUsername("test_100").orElseThrow(); + assignGroupManager(account, group); + IamGroupRequest request = savePendingRequest(account, group, 5); + request.setStatus(IamGroupRequestStatus.APPROVED); + groupRequestRepo.save(request); + + reminderTask.sendReminders(); + + assertThat(getReminders(), hasSize(0)); + } + + @Test + void fallsBackToAdminsWhenNoGroupManager() { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + savePendingRequest(accountRepo.findByUsername("test_100").orElseThrow(), group, 5); + + reminderTask.sendReminders(); + + assertThat(getReminders(), hasSize(1)); + } + + @Test + void reminderGroupsMultipleUsersPerGroup() { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + IamAccount a1 = accountRepo.findByUsername("test_100").orElseThrow(); + IamAccount a2 = accountRepo.findByUsername("test_101").orElseThrow(); + assignGroupManager(a1, group); + savePendingRequest(a1, group, 5); + savePendingRequest(a2, group, 3); + + reminderTask.sendReminders(); + + List reminders = getReminders(); + assertThat(reminders, hasSize(1)); + assertThat(reminders.get(0).getBody(), containsString("test_100")); + assertThat(reminders.get(0).getBody(), containsString("test_101")); + } + + @Test + void doesNothingWhenDisabled() throws Exception { + setTaskField("enabled", false); + try { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + savePendingRequest(accountRepo.findByUsername("test_100").orElseThrow(), group, 5); + + reminderTask.sendReminders(); + + assertThat(getReminders(), hasSize(0)); + } finally { + setTaskField("enabled", true); + } + } + + @Test + void includesAdminsWhenConfigured() throws Exception { + setTaskField("notifyAdmins", true); + try { + IamGroup group = groupRepo.findByName(GROUP_NAME).orElseThrow(); + IamAccount account = accountRepo.findByUsername("test_100").orElseThrow(); + assignGroupManager(account, group); + savePendingRequest(account, group, 5); + + reminderTask.sendReminders(); + + List reminders = getReminders(); + assertThat(reminders, hasSize(1)); + assertThat(reminders.get(0).getReceivers().size(), greaterThanOrEqualTo(2)); + } finally { + setTaskField("notifyAdmins", false); + } + } + + private List getReminders() { + return emailRepo.findByNotificationType(IamNotificationType.GROUP_MEMBERSHIP_REMINDER); + } + + private void setTaskField(String name, Object value) throws Exception { + Field field = GroupRequestReminderTask.class.getDeclaredField(name); + field.setAccessible(true); + field.set(reminderTask, value); + } + + private void assignGroupManager(IamAccount account, IamGroup group) { + String gmAuth = String.format("ROLE_GM:%s", group.getUuid()); + IamAuthority auth = authoritiesRepo.findByAuthority(gmAuth) + .orElseGet(() -> authoritiesRepo.save(new IamAuthority(gmAuth))); + account.getAuthorities().add(auth); + accountRepo.save(account); + } + + private IamGroupRequest savePendingRequest(IamAccount account, IamGroup group, int daysAgo) { + IamGroupRequest request = new IamGroupRequest(); + request.setUuid(UUID.randomUUID().toString()); + request.setAccount(account); + request.setGroup(group); + request.setStatus(IamGroupRequestStatus.PENDING); + request.setNotes("Test request"); + request.setCreationTime(Date.from( + LocalDate.now().minusDays(daysAgo).atStartOfDay(ZoneId.systemDefault()).toInstant())); + return groupRequestRepo.save(request); + } +} diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java b/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java index 24efd30f76..6685827163 100644 --- a/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java @@ -17,7 +17,8 @@ public enum IamNotificationType { - CONFIRMATION, RESETPASSWD, ACTIVATED, REJECTED, GROUP_MEMBERSHIP, AUP_REMINDER, AUP_EXPIRATION, + CONFIRMATION, RESETPASSWD, ACTIVATED, REJECTED, GROUP_MEMBERSHIP, + GROUP_MEMBERSHIP_REMINDER, AUP_REMINDER, AUP_EXPIRATION, AUP_SIGNATURE_REQUEST, ACCOUNT_SUSPENDED, ACCOUNT_RESTORED, CLIENT_STATUS, CERTIFICATE_LINK, CERTIFICATE_UNLINK, MFA_ENABLE, MFA_DISABLE, SET_SERVICE_ACCOUNT, REVOKE_SERVICE_ACCOUNT; } diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java index 153b6155fc..1cc1e33c60 100644 --- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java @@ -59,4 +59,12 @@ Integer countAupRemindersPerAccount(@Param("email_address") String emailAddress, + " and r.emailAddress = :email_address") Integer countAupExpirationMessPerAccount(@Param("email_address") String emailAddress); + @Query("select count(n) from IamEmailNotification n" + + " where n.notificationType = it.infn.mw.iam.core.IamNotificationType.GROUP_MEMBERSHIP_REMINDER" + + " and n.subject like :subjectPattern" + + " and n.creationTime >= :since" + + " and n.deliveryStatus <> it.infn.mw.iam.core.IamDeliveryStatus.DELIVERY_ERROR") + Integer countGroupMembershipReminders(@Param("subjectPattern") String subjectPattern, + @Param("since") Date since); + } diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRequestRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRequestRepository.java index ef12923ef6..bb0550d46e 100644 --- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRequestRepository.java +++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamGroupRequestRepository.java @@ -15,6 +15,7 @@ */ package it.infn.mw.iam.persistence.repository; +import java.util.Date; import java.util.List; import java.util.Optional; @@ -36,4 +37,7 @@ List findByUsernameAndGroup(@Param("username") String username, @Param("groupName") String groupName); Optional findByGroupIdAndAccountIdAndStatus(Long groupID, Long accountID, IamGroupRequestStatus status); + + @Query("select r from IamGroupRequest r where r.status = :status and r.creationTime < :cutoffDate order by r.creationTime asc") + List findPendingRequestsOlderThan(@Param("status") IamGroupRequestStatus status, @Param("cutoffDate") Date cutoffDate); }