From f1171c11ad4a1f95c490b932de2db7fa7614cefa Mon Sep 17 00:00:00 2001 From: aindriu-aiven <121855584+aindriu-aiven@users.noreply.github.com> Date: Fri, 19 May 2023 15:14:04 +0100 Subject: [PATCH] Issue email to all approvers (#1258) * Send an Approval email to everyone that is able to approve the request. Signed-off-by: Aindriu Lavelle * remove redundant import Signed-off-by: Aindriu Lavelle * Add unit tests for MailUtils Signed-off-by: Aindriu Lavelle --------- Signed-off-by: Aindriu Lavelle --- .../io/aiven/klaw/config/ManageDatabase.java | 1 - .../io/aiven/klaw/service/EmailService.java | 19 ++- .../java/io/aiven/klaw/service/MailUtils.java | 74 +++++++++++- .../io/aiven/klaw/service/MailUtilsTest.java | 111 ++++++++++++++++-- 4 files changed, 187 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/io/aiven/klaw/config/ManageDatabase.java b/core/src/main/java/io/aiven/klaw/config/ManageDatabase.java index b0128588ea..c71da416db 100644 --- a/core/src/main/java/io/aiven/klaw/config/ManageDatabase.java +++ b/core/src/main/java/io/aiven/klaw/config/ManageDatabase.java @@ -35,7 +35,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.swing.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; diff --git a/core/src/main/java/io/aiven/klaw/service/EmailService.java b/core/src/main/java/io/aiven/klaw/service/EmailService.java index bf851c25b7..0e161edfc5 100644 --- a/core/src/main/java/io/aiven/klaw/service/EmailService.java +++ b/core/src/main/java/io/aiven/klaw/service/EmailService.java @@ -10,6 +10,7 @@ import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import java.io.UnsupportedEncodingException; +import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import lombok.extern.slf4j.Slf4j; @@ -71,10 +72,21 @@ public class EmailService { + "\t\n" + ""; - @Async("notificationsThreadPool") public void sendSimpleMessage( String to, String cc, String subject, String text, int tenantId, String loginUrl) { + sendSimpleMessage(to, cc, null, subject, text, tenantId, loginUrl); + } + + @Async("notificationsThreadPool") + public void sendSimpleMessage( + String to, + String cc, + List bcc, + String subject, + String text, + int tenantId, + String loginUrl) { String emailNotificationsEnabled = manageDatabase.getKwPropertyValue(EMAIL_NOTIFICATIONS_ENABLED_KEY, DEFAULT_TENANT_ID); try { @@ -83,6 +95,11 @@ public void sendSimpleMessage( if (cc != null) { message.setRecipients(Message.RecipientType.CC, cc); } + if (bcc != null && !bcc.isEmpty()) { + for (String bccAddress : bcc) { + message.setRecipients(Message.RecipientType.BCC, bccAddress); + } + } message.setSubject(subject); Address address = new InternetAddress(noReplyMailId); diff --git a/core/src/main/java/io/aiven/klaw/service/MailUtils.java b/core/src/main/java/io/aiven/klaw/service/MailUtils.java index 6d91bda01b..0dc136194b 100644 --- a/core/src/main/java/io/aiven/klaw/service/MailUtils.java +++ b/core/src/main/java/io/aiven/klaw/service/MailUtils.java @@ -9,7 +9,10 @@ import io.aiven.klaw.helpers.UtilMethods; import io.aiven.klaw.model.enums.ApiResultStatus; import io.aiven.klaw.model.enums.MailType; +import io.aiven.klaw.model.enums.PermissionType; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -109,19 +112,22 @@ void sendMail( getUserName(SecurityContextHolder.getContext().getAuthentication().getPrincipal())) .getTenantId(); loadKwProps(tenantId); - + Boolean requiresApproval = false; switch (mailType) { case TOPIC_CREATE_REQUESTED -> { formattedStr = String.format(topicRequestMail, "'" + topicName + "'"); subject = "Create Topic Request"; + requiresApproval = true; } case TOPIC_DELETE_REQUESTED -> { formattedStr = String.format(topicDeleteRequestMail, "'" + topicName + "'"); subject = "Delete Topic Request"; + requiresApproval = true; } case TOPIC_CLAIM_REQUESTED -> { formattedStr = String.format(topicClaimRequestMail, "'" + topicName + "'"); subject = "Claim Topic Request"; + requiresApproval = true; } case TOPIC_REQUEST_APPROVED -> { formattedStr = String.format(topicRequestApproved, "'" + topicName + "'"); @@ -135,10 +141,12 @@ void sendMail( case ACL_REQUESTED -> { formattedStr = String.format(aclRequestMail, "'" + acl + "'", "'" + topicName + "'"); subject = "New Acl Request"; + requiresApproval = true; } case ACL_DELETE_REQUESTED -> { formattedStr = String.format(aclDeleteRequestMail, "'" + acl + "'", "'" + topicName + "'"); subject = "Acl Delete Request"; + requiresApproval = true; } case ACL_REQUEST_APPROVED -> { formattedStr = String.format(aclRequestApproved, "'" + acl + "'", "'" + topicName + "'"); @@ -157,9 +165,25 @@ void sendMail( formattedStr = "Acl Request processing failed : " + acl + ", " + topicName; subject = "Request processing failed."; } + case CONNECTOR_CLAIM_REQUESTED, + CONNECTOR_REQUEST_DENIED, + CONNECTOR_DELETE_REQUESTED, + SCHEMA_REQUESTED -> { + // all remaining requests that require approvals are grouped here. + requiresApproval = true; + } } - sendMail(username, dbHandle, formattedStr, subject, false, null, tenantId, loginUrl); + sendMail( + username, + dbHandle, + formattedStr, + subject, + false, + requiresApproval, + null, + tenantId, + loginUrl); } void sendMail(String username, String pwd, HandleDbRequests dbHandle, String loginUrl) { @@ -174,7 +198,7 @@ void sendMail(String username, String pwd, HandleDbRequests dbHandle, String log formattedStr = String.format(newUserAdded, username, pwd); subject = "Access to Klaw"; - sendMail(username, dbHandle, formattedStr, subject, false, null, tenantId, loginUrl); + sendMail(username, dbHandle, formattedStr, subject, false, false, null, tenantId, loginUrl); } void sendMailResetPwd( @@ -255,6 +279,7 @@ void sendMailRegisteredUserSaas( formattedStr, subject, true, + false, registerUserInfo.getMailid(), tenantId, loginUrl); @@ -296,6 +321,7 @@ void sendMailRegisteredUser( formattedStr, subject, true, + false, registerUserInfo.getMailid(), tenantId, loginUrl); @@ -340,6 +366,7 @@ private void sendMail( String formattedStr, String subject, boolean registrationRequest, + boolean requiresApproval, String otherMailId, int tenantId, String loginUrl) { @@ -349,6 +376,8 @@ private void sendMail( String emailId; String emailIdTeam = null; + Integer teamId = null; + List allApprovers = null; try { if (registrationRequest) { emailId = otherMailId; @@ -358,14 +387,19 @@ private void sendMail( try { List allTeams = dbHandle.getAllTeamsOfUsers(username, tenantId); - if (!allTeams.isEmpty()) emailIdTeam = allTeams.get(0).getTeammail(); + if (!allTeams.isEmpty()) { + emailIdTeam = allTeams.get(0).getTeammail(); + teamId = allTeams.get(0).getTeamId(); + } } catch (Exception e) { log.error("Exception :", e); } - + if (requiresApproval) { + allApprovers = getAllUsersWithPermissionToApproveRequest(tenantId, username, teamId); + } if (emailId != null) { emailService.sendSimpleMessage( - emailId, emailIdTeam, subject, formattedStr, tenantId, loginUrl); + emailId, emailIdTeam, allApprovers, subject, formattedStr, tenantId, loginUrl); } else { log.error("Email id not found. Notification not sent !!"); } @@ -417,6 +451,34 @@ public String getEmailAddressFromUsername(String username) { } } + private List getAllUsersWithPermissionToApproveRequest( + int tenantId, String username, Integer teamId) { + + Map> rolesToPermissions = + manageDatabase.getRolesPermissionsPerTenant(tenantId); + + List roles = new ArrayList<>(); + + rolesToPermissions.forEach( + (k, v) -> { + if (v.contains(PermissionType.APPROVE_ALL_REQUESTS_TEAMS.name())) { + roles.add(k); + } + }); + + // Prevent duplicates only show from the correct tenant, that is not the usernae of the + // requestor and that is not on the same team as they have already received that email. + return manageDatabase.selectAllCachedUserInfo().stream() + .filter( + user -> + user.getTenantId() == tenantId + && !user.getUsername().equals(username) + && roles.contains(user.getRole()) + && (teamId == null || !user.getTeamId().equals(teamId))) + .map(u -> u.getMailid()) + .toList(); + } + public String sendMailToSaasAdmin(int tenantId, String userName, String period, String loginUrl) { String mailtext = "Tenant extension : Tenant " + tenantId + " username " + userName + " period " + period; diff --git a/core/src/test/java/io/aiven/klaw/service/MailUtilsTest.java b/core/src/test/java/io/aiven/klaw/service/MailUtilsTest.java index 899da6c7d0..33a57e2548 100644 --- a/core/src/test/java/io/aiven/klaw/service/MailUtilsTest.java +++ b/core/src/test/java/io/aiven/klaw/service/MailUtilsTest.java @@ -7,9 +7,13 @@ import io.aiven.klaw.config.ManageDatabase; import io.aiven.klaw.dao.UserInfo; -import io.aiven.klaw.helpers.HandleDbRequests; import io.aiven.klaw.helpers.KwConstants; +import io.aiven.klaw.helpers.db.rdbms.HandleDbRequestsJdbc; +import io.aiven.klaw.model.enums.MailType; +import io.aiven.klaw.model.enums.PermissionType; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -17,6 +21,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; @ExtendWith(SpringExtension.class) @@ -25,7 +30,9 @@ public class MailUtilsTest { public static final String LOGIN_URL = "https://localhost:9097"; @Mock UserDetails userDetails; - @Mock HandleDbRequests handleDbRequests; + // @Mock HandleDbRequests handleDbRequests; + + @Mock HandleDbRequestsJdbc handleDbRequestsJdbc; @Mock EmailService emailService; @@ -36,6 +43,11 @@ public class MailUtilsTest { @BeforeEach public void setUp() throws Exception { // mailService = new MailUtils(); + when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc); + when(handleDbRequestsJdbc.getUsersInfo(eq("james"))) + .thenReturn(createUserInfo("James", "USER")); + when(manageDatabase.getRolesPermissionsPerTenant(eq(101))) + .thenReturn(getRolesToPermissionsMap()); } @Test @@ -45,13 +57,11 @@ public void getUserDetails() {} public void resetPasswordEmail_noCCTeam() throws InterruptedException { String username = "Octopus"; - UserInfo info = new UserInfo(); - info.setUsername(username); - info.setMailid("Octopus.klaw@mailid"); + UserInfo info = createUserInfo(username, "USER"); when(manageDatabase.selectAllCachedUserInfo()).thenReturn(List.of(info)); when(manageDatabase.getKwPropertyValue(eq("klaw.mail.passwordreset.content"), eq(101))) .thenReturn(KwConstants.MAIL_PASSWORDRESET_CONTENT); - mailService.sendMailResetPwd(username, "KlawPassword", handleDbRequests, 101, LOGIN_URL); + mailService.sendMailResetPwd(username, "KlawPassword", handleDbRequestsJdbc, 101, LOGIN_URL); Thread.sleep(1000); Mockito.verify(emailService, timeout(1000).times(1)) @@ -63,15 +73,96 @@ public void resetPasswordEmail_noCCTeam() throws InterruptedException { public void resetPasswordEmail_noSuchUser() { String username = "Octopus"; - UserInfo info = new UserInfo(); - info.setUsername("Octi"); - info.setMailid("Octi.klaw@mailid"); + UserInfo info = createUserInfo("Octi", "USER"); when(manageDatabase.selectAllCachedUserInfo()).thenReturn(List.of(info)); when(manageDatabase.getKwPropertyValue(eq("klaw.mail.passwordreset.content"), eq(101))) .thenReturn(KwConstants.MAIL_PASSWORDRESET_CONTENT); - mailService.sendMailResetPwd(username, "KlawPassword", handleDbRequests, 101, LOGIN_URL); + mailService.sendMailResetPwd(username, "KlawPassword", handleDbRequestsJdbc, 101, LOGIN_URL); Mockito.verify(emailService, timeout(1000).times(0)) .sendSimpleMessage( eq(info.getMailid()), eq(null), anyString(), anyString(), eq(101), eq(LOGIN_URL)); } + + @Test + @WithMockUser( + username = "james", + authorities = {"USER"}) + public void sendMailWith_NoBCC() { + String username = "James"; + when(manageDatabase.selectAllCachedUserInfo()) + .thenReturn(List.of(createUserInfo(username, "USER"), createUserInfo("Tom", "ADMIN"))); + when(manageDatabase.getKwPropertyValue(eq("klaw.mail.aclrequestapproval.content"), eq(101))) + .thenReturn(KwConstants.MAIL_ACLREQUESTAPPROVAL_CONTENT); + + mailService.sendMail( + "TopicOne", + "AclOne", + null, + username, + handleDbRequestsJdbc, + MailType.ACL_REQUEST_APPROVED, + LOGIN_URL); + + Mockito.verify(emailService, timeout(1000).times(1)) + .sendSimpleMessage( + anyString(), eq(null), eq(null), anyString(), anyString(), eq(101), eq(LOGIN_URL)); + } + + @Test + @WithMockUser( + username = "james", + authorities = {"USER"}) + public void sendMailWith_BCC() { + + String username = "James"; + when(manageDatabase.selectAllCachedUserInfo()) + .thenReturn(List.of(createUserInfo(username, "USER"), createUserInfo("Tom", "ADMIN"))); + when(manageDatabase.getKwPropertyValue(eq("klaw.mail.topicrequest.content"), eq(101))) + .thenReturn(KwConstants.MAIL_TOPICREQUEST_CONTENT); + + mailService.sendMail( + "TopicOne", + "AclOne", + null, + username, + handleDbRequestsJdbc, + MailType.TOPIC_CREATE_REQUESTED, + LOGIN_URL); + + Mockito.verify(emailService, timeout(1000).times(1)) + .sendSimpleMessage( + anyString(), + eq(null), + eq(List.of("Tom.klaw@mailid")), + anyString(), + anyString(), + eq(101), + eq(LOGIN_URL)); + } + + private Map> getRolesToPermissionsMap() { + + List user = List.of(PermissionType.APPROVE_TOPICS.name()); + + List admin = + List.of( + PermissionType.APPROVE_TOPICS.name(), PermissionType.APPROVE_ALL_REQUESTS_TEAMS.name()); + + return new HashMap<>() { + { + put("USER", user); + put("ADMIN", admin); + } + }; + } + + private static UserInfo createUserInfo(String username, String role) { + UserInfo info = new UserInfo(); + info.setUsername(username); + info.setMailid(username + ".klaw@mailid"); + info.setTenantId(101); + info.setTeamId(10); + info.setRole(role); + return info; + } }