|
1 | 1 | package cz.metacentrum.perun.registrar.impl; |
2 | 2 |
|
3 | | -import java.io.*; |
| 3 | +import java.io.IOException; |
| 4 | +import java.io.UnsupportedEncodingException; |
4 | 5 | import java.math.BigInteger; |
5 | 6 | import java.net.URLEncoder; |
6 | 7 | import java.nio.charset.StandardCharsets; |
7 | | -import java.util.*; |
| 8 | +import java.util.ArrayList; |
| 9 | +import java.util.Arrays; |
| 10 | +import java.util.Collections; |
| 11 | +import java.util.HashMap; |
| 12 | +import java.util.HashSet; |
| 13 | +import java.util.LinkedHashMap; |
| 14 | +import java.util.List; |
| 15 | +import java.util.Locale; |
| 16 | +import java.util.Map; |
| 17 | +import java.util.Properties; |
| 18 | +import java.util.Set; |
8 | 19 | import java.util.regex.Matcher; |
9 | 20 | import java.util.regex.Pattern; |
10 | 21 |
|
|
32 | 43 | import cz.metacentrum.perun.audit.events.MailManagerEvents.MailForVoIdUpdated; |
33 | 44 | import cz.metacentrum.perun.audit.events.MailManagerEvents.MailSending; |
34 | 45 | import cz.metacentrum.perun.audit.events.MailManagerEvents.MailSentForApplication; |
35 | | -import cz.metacentrum.perun.core.api.*; |
36 | | -import cz.metacentrum.perun.core.api.exceptions.*; |
| 46 | +import cz.metacentrum.perun.core.api.Attribute; |
| 47 | +import cz.metacentrum.perun.core.api.AuthzResolver; |
| 48 | +import cz.metacentrum.perun.core.api.BeansUtils; |
| 49 | +import cz.metacentrum.perun.core.api.ExtSourcesManager; |
| 50 | +import cz.metacentrum.perun.core.api.Group; |
| 51 | +import cz.metacentrum.perun.core.api.Member; |
| 52 | +import cz.metacentrum.perun.core.api.PerunBean; |
| 53 | +import cz.metacentrum.perun.core.api.PerunClient; |
| 54 | +import cz.metacentrum.perun.core.api.PerunPrincipal; |
| 55 | +import cz.metacentrum.perun.core.api.PerunSession; |
| 56 | +import cz.metacentrum.perun.core.api.RichUser; |
| 57 | +import cz.metacentrum.perun.core.api.Role; |
| 58 | +import cz.metacentrum.perun.core.api.RoleManagementRules; |
| 59 | +import cz.metacentrum.perun.core.api.User; |
| 60 | +import cz.metacentrum.perun.core.api.Vo; |
| 61 | +import cz.metacentrum.perun.core.api.exceptions.AttributeNotExistsException; |
| 62 | +import cz.metacentrum.perun.core.api.exceptions.ConsistencyErrorException; |
| 63 | +import cz.metacentrum.perun.core.api.exceptions.GroupNotExistsException; |
| 64 | +import cz.metacentrum.perun.core.api.exceptions.InternalErrorException; |
| 65 | +import cz.metacentrum.perun.core.api.exceptions.InvalidHtmlInputException; |
| 66 | +import cz.metacentrum.perun.core.api.exceptions.PerunException; |
| 67 | +import cz.metacentrum.perun.core.api.exceptions.PrivilegeException; |
| 68 | +import cz.metacentrum.perun.core.api.exceptions.RoleCannotBeManagedException; |
| 69 | +import cz.metacentrum.perun.core.api.exceptions.VoNotExistsException; |
| 70 | +import cz.metacentrum.perun.core.api.exceptions.WrongAttributeAssignmentException; |
37 | 71 | import cz.metacentrum.perun.core.bl.AttributesManagerBl; |
38 | 72 | import cz.metacentrum.perun.core.bl.GroupsManagerBl; |
39 | 73 | import cz.metacentrum.perun.core.bl.MembersManagerBl; |
|
56 | 90 | import org.springframework.mail.MailException; |
57 | 91 | import org.springframework.mail.javamail.JavaMailSender; |
58 | 92 | import org.springframework.transaction.annotation.Transactional; |
| 93 | +import org.springframework.util.StringUtils; |
59 | 94 |
|
60 | 95 | import cz.metacentrum.perun.core.bl.PerunBl; |
61 | 96 | import org.springframework.jdbc.core.JdbcPerunTemplate; |
|
71 | 106 | import cz.metacentrum.perun.registrar.MailManager; |
72 | 107 | import cz.metacentrum.perun.registrar.RegistrarManager; |
73 | 108 |
|
74 | | -import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.*; |
75 | 109 | import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_FROM_EMAIL; |
76 | 110 | import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_FROM_NAME_EMAIL; |
| 111 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_HTML_MAIL_FOOTER; |
| 112 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_LANGUAGE_EMAIL; |
| 113 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_MAIL_FOOTER; |
| 114 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_REGISTRAR_URL; |
| 115 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_GROUP_TO_EMAIL; |
77 | 116 | import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_FROM_EMAIL; |
| 117 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_FROM_NAME_EMAIL; |
| 118 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_HTML_MAIL_FOOTER; |
| 119 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_LANGUAGE_EMAIL; |
| 120 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_MAIL_FOOTER; |
| 121 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_REGISTRAR_URL; |
| 122 | +import static cz.metacentrum.perun.registrar.impl.RegistrarManagerImpl.URN_VO_TO_EMAIL; |
78 | 123 |
|
79 | 124 | public class MailManagerImpl implements MailManager { |
80 | 125 |
|
@@ -524,22 +569,16 @@ public void sendMessage(Application app, MailType mailType, String reason, List< |
524 | 569 | // different behavior based on mail type |
525 | 570 | switch (mail.getMailType()) { |
526 | 571 | case APP_CREATED_USER: |
527 | | - sendUserMessage(app, mail, data, reason, exceptions, MailType.APP_CREATED_USER); |
528 | | - break; |
529 | 572 | case APPROVABLE_GROUP_APP_USER: |
530 | | - sendUserMessage(app, mail, data, reason, exceptions, MailType.APPROVABLE_GROUP_APP_USER); |
531 | | - break; |
532 | | - case APP_CREATED_VO_ADMIN: |
533 | | - appCreatedVoAdmin(app, mail, data, reason, exceptions); |
| 573 | + case APP_APPROVED_USER: |
| 574 | + case APP_REJECTED_USER: |
| 575 | + sendUserMessage(app, mail, data, reason, exceptions, mail.getMailType()); |
534 | 576 | break; |
535 | 577 | case MAIL_VALIDATION: |
536 | 578 | mailValidation(app, mail, data, reason, exceptions); |
537 | 579 | break; |
538 | | - case APP_APPROVED_USER: |
539 | | - sendUserMessage(app, mail, data, reason, exceptions, MailType.APP_APPROVED_USER); |
540 | | - break; |
541 | | - case APP_REJECTED_USER: |
542 | | - sendUserMessage(app, mail, data, reason, exceptions, MailType.APP_REJECTED_USER); |
| 580 | + case APP_CREATED_VO_ADMIN: |
| 581 | + appCreatedVoAdmin(app, mail, data, reason, exceptions); |
543 | 582 | break; |
544 | 583 | case APP_ERROR_VO_ADMIN: |
545 | 584 | appErrorVoAdmin(app, mail, data, reason, exceptions); |
@@ -1110,69 +1149,57 @@ private void setFromAndReplyTo(MimeMessage message, String fromName, String from |
1110 | 1149 | /** |
1111 | 1150 | * Get proper values "TO" for mail message based on VO or GROUP attribute "toEmail". |
1112 | 1151 | * <p> |
1113 | | - * If group attribute not set and is group application, notify all corresponding group admins (admin roles configured to receive GROUP notifications). |
1114 | | - * In case no such admins not set, use VO attribute. |
1115 | | - * If Vo attribute not set (both group or vo application), notify all corresponding vo admins (admin roles configured to receive VO notifications). |
1116 | | - * Otherwise, BACKUP_FROM address will be used. |
| 1152 | + * If group attribute is not set and Application is a group application, notify all corresponding group admins |
| 1153 | + * (admin roles configured to receive GROUP notifications). |
| 1154 | + * In case no such admins are set, try the same procedure on parent group (if exists) |
| 1155 | + * When reaches the top-level group and no recipients have been identified, repeat the same procedure on VO level (without cascading). |
| 1156 | + * When no recipient is identified on the VO level or some error happens, BACKUP_TO address will be used. |
1117 | 1157 | * |
1118 | 1158 | * @param app application to decide if it's VO or Group application |
1119 | | - * @return list of mail addresses to send mail to |
| 1159 | + * @return Set of mail addresses to send mail to |
1120 | 1160 | */ |
1121 | | - private List<String> getToMailAddresses(Application app) { |
1122 | | - List<String> result = new ArrayList<>(); |
1123 | | - |
1124 | | - // get proper value from attribute |
| 1161 | + private Set<String> getToMailAddresses(Application app) { |
| 1162 | + Set<String> result = new HashSet<>(); |
1125 | 1163 | try { |
1126 | | - Attribute attrSenderEmail; |
1127 | | - |
1128 | | - if (app.getGroup() == null) { |
1129 | | - // it is a VO application |
1130 | | - return getMailsFromVo(app, URN_VO_TO_EMAIL); |
1131 | | - } |
1132 | | - |
1133 | | - attrSenderEmail = attrManager.getAttribute(registrarSession, app.getGroup(), URN_GROUP_TO_EMAIL); |
1134 | | - |
1135 | | - if (attrSenderEmail == null || attrSenderEmail.getValue() == null) { |
1136 | | - // not specified toEmail group attribute |
1137 | | - |
1138 | | - // try to use all group admins with specified preferred emails |
1139 | | - List<String> rolesToNotify = getNotificationReceiverRoles(GROUP); |
1140 | | - List<RichUser> admins = new ArrayList<>(); |
1141 | | - for (String role : rolesToNotify) { |
1142 | | - admins.addAll(AuthzResolverBlImpl.getRichAdmins(registrarSession, app.getGroup(), role)); |
1143 | | - } |
1144 | | - result.addAll(getUserPreferredMails(registrarSession, admins)); |
1145 | | - result = result.stream().distinct().toList(); |
1146 | | - |
| 1164 | + // Check group and its parent group hierarchy first |
| 1165 | + Group currentGroup = app.getGroup(); |
| 1166 | + while (currentGroup != null) { |
| 1167 | + result = getToEmailsGroupLevel(currentGroup); |
1147 | 1168 | if (!result.isEmpty()) { |
1148 | | - return result; |
| 1169 | + break; |
1149 | 1170 | } |
1150 | | - return getMailsFromVo(app, URN_VO_TO_EMAIL); |
| 1171 | + currentGroup = getParentGroupForMailAddresses(currentGroup); |
1151 | 1172 | } |
1152 | | - |
1153 | | - // specified toEmail group attribute, use valid emails |
1154 | | - ArrayList<String> value = attrSenderEmail.valueAsList(); |
1155 | | - for (String adr : value) { |
1156 | | - if (adr != null && !adr.isEmpty()) { |
1157 | | - result.add(adr); |
1158 | | - } |
| 1173 | + // VO application or fallback to VO level |
| 1174 | + if (app.getGroup() == null || result.isEmpty()) { |
| 1175 | + result = getToEmailsVoLevel(app.getVo()); |
1159 | 1176 | } |
1160 | | - |
1161 | 1177 | } catch (Exception ex) { |
1162 | 1178 | // we don't care about exceptions here - we have backup TO/FROM address |
1163 | | - if (app.getGroup() == null) { |
1164 | | - log.error("[MAIL MANAGER] Exception thrown when getting TO email from an attribute {}. Ex: {}", URN_VO_TO_EMAIL, ex); |
1165 | | - } else { |
1166 | | - log.error("[MAIL MANAGER] Exception thrown when getting TO email from an attribute {}. Ex: {}", URN_GROUP_TO_EMAIL, ex); |
1167 | | - } |
| 1179 | + log.error("[MAIL MANAGER] Exception thrown when getting TO email from an attribute {}. Ex: {}", |
| 1180 | + app.getGroup() == null ? URN_VO_TO_EMAIL : URN_GROUP_TO_EMAIL, ex); |
1168 | 1181 | // set backup |
1169 | 1182 | result.clear(); |
| 1183 | + } |
| 1184 | + // no recipients found for Group and Vo, use backup |
| 1185 | + if (result.isEmpty()) { |
1170 | 1186 | result.add(getPropertyFromConfiguration("backupTo")); |
1171 | 1187 | } |
1172 | | - |
1173 | 1188 | return result; |
1174 | 1189 | } |
1175 | 1190 |
|
| 1191 | + private Group getParentGroupForMailAddresses(Group group) { |
| 1192 | + if (group != null && group.getParentGroupId() != null) { |
| 1193 | + try { |
| 1194 | + return groupsManager.getGroupById(registrarSession, group.getParentGroupId()); |
| 1195 | + } catch (GroupNotExistsException e) { |
| 1196 | + log.error("[MAIL MANAGER] Inconsistency detected - parent (in hierarchy) group with ID '{}' of group '{}' does not exist", |
| 1197 | + group.getParentGroupId(), group); |
| 1198 | + } |
| 1199 | + } |
| 1200 | + return null; |
| 1201 | + } |
| 1202 | + |
1176 | 1203 | /** |
1177 | 1204 | * Returns list of roles, which should be notified for object's notification type |
1178 | 1205 | * @return list of role names |
@@ -2006,7 +2033,7 @@ private void appCreatedVoAdmin(Application app, ApplicationMail mail, List<Appli |
2006 | 2033 | MimeMessage message = getAdminMessage(app, mail, data, reason, exceptions); |
2007 | 2034 |
|
2008 | 2035 | // send a message to all VO or Group admins |
2009 | | - List<String> toEmail = getToMailAddresses(app); |
| 2036 | + Set<String> toEmail = getToMailAddresses(app); |
2010 | 2037 | for (String email : toEmail) { |
2011 | 2038 | setRecipient(message, email); |
2012 | 2039 | try { |
@@ -2163,7 +2190,7 @@ private void appErrorVoAdmin(Application app, ApplicationMail mail, List<Applica |
2163 | 2190 | MimeMessage message = getAdminMessage(app, mail, data, reason, exceptions); |
2164 | 2191 |
|
2165 | 2192 | // send a message to all VO or Group admins |
2166 | | - List<String> toEmail = getToMailAddresses(app); |
| 2193 | + Set<String> toEmail = getToMailAddresses(app); |
2167 | 2194 |
|
2168 | 2195 | for (String email : toEmail) { |
2169 | 2196 | setRecipient(message, email); |
@@ -2557,45 +2584,90 @@ private void setHtmlMessageWithAltPlainTextMessage (MimeMessage message, String |
2557 | 2584 | } |
2558 | 2585 |
|
2559 | 2586 | /** |
2560 | | - * Get notification emails for given VO. If toEmail is not specified, |
| 2587 | + * Get notification emails for given VO. If toEmail (URN_VO_TO_EMAIL) has no value set, |
2561 | 2588 | * all configured roles for receiving VO notifications are used. |
2562 | 2589 | * |
2563 | | - * @param app Application to get VO from |
2564 | | - * @param voEmailAttr String name of given email attribute |
2565 | | - * @return list of emails used for sending notifications. |
| 2590 | + * @param vo Virtual Organization for which the lookup is performed. If null, empty result will be returned. |
| 2591 | + * @return List of emails used for sending notifications (can be empty). |
2566 | 2592 | */ |
2567 | | - private List<String> getMailsFromVo(Application app, String voEmailAttr) |
2568 | | - throws AttributeNotExistsException, WrongAttributeAssignmentException { |
2569 | | - |
2570 | | - List<String> emails = new ArrayList<>(); |
| 2593 | + private Set<String> getToEmailsVoLevel(Vo vo) |
| 2594 | + throws AttributeNotExistsException, WrongAttributeAssignmentException |
| 2595 | + { |
| 2596 | + if (vo == null) { |
| 2597 | + return new HashSet<>(); |
| 2598 | + } |
| 2599 | + Attribute toEmailAttr = attrManager.getAttribute(registrarSession, vo, URN_VO_TO_EMAIL); |
| 2600 | + Set<String> emails = getToEmailsFromAttribute(toEmailAttr); |
| 2601 | + if (emails.isEmpty()) { |
| 2602 | + emails = getToEmailsViaRoles(VO, vo); |
| 2603 | + } |
| 2604 | + return emails; |
| 2605 | + } |
2571 | 2606 |
|
2572 | | - Attribute attrToEmail = attrManager.getAttribute(registrarSession, app.getVo(), voEmailAttr); |
| 2607 | + /** |
| 2608 | + * Get notification emails for given Group. If toEmail (URN_GROUP_TO_EMAIL) has no value set, |
| 2609 | + * all configured roles for receiving GROUP notifications are used. |
| 2610 | + * |
| 2611 | + * @param group Group for which the lookup is performed. If null, empty result will be returned. |
| 2612 | + * @return List of emails used for sending notifications (can be empty). |
| 2613 | + */ |
| 2614 | + private Set<String> getToEmailsGroupLevel(Group group) |
| 2615 | + throws AttributeNotExistsException, WrongAttributeAssignmentException |
| 2616 | + { |
| 2617 | + if (group == null) { |
| 2618 | + return new HashSet<>(); |
| 2619 | + } |
| 2620 | + Attribute toEmailAttr = attrManager.getAttribute(registrarSession, group, URN_GROUP_TO_EMAIL); |
| 2621 | + Set<String> emails = getToEmailsFromAttribute(toEmailAttr); |
| 2622 | + if (emails.isEmpty()) { |
| 2623 | + emails = getToEmailsViaRoles(GROUP, group); |
| 2624 | + } |
| 2625 | + return emails; |
| 2626 | + } |
2573 | 2627 |
|
| 2628 | + /** |
| 2629 | + * Get "TO" email addresses of notification recipient(s) from the attribute |
| 2630 | + * @param toEmailAttr Attribute with the recipients as value |
| 2631 | + * @return Set of identified emails. Can be empty if attr has no non-blank item in array value or no value at all. |
| 2632 | + */ |
| 2633 | + private Set<String> getToEmailsFromAttribute(Attribute toEmailAttr) { |
2574 | 2634 | // if there are any pre-defined VO emailTo attributes, use them |
2575 | | - if (attrToEmail != null && attrToEmail.getValue() != null) { |
2576 | | - ArrayList<String> value = attrToEmail.valueAsList(); |
| 2635 | + Set<String> emails = new HashSet<>(); |
| 2636 | + if (toEmailAttr != null && toEmailAttr.getValue() != null) { |
| 2637 | + ArrayList<String> value = toEmailAttr.valueAsList(); |
2577 | 2638 | for (String adr : value) { |
2578 | | - if (adr != null && !adr.isEmpty()) { |
| 2639 | + if (StringUtils.hasText(adr)) { |
2579 | 2640 | emails.add(adr); |
2580 | 2641 | } |
2581 | 2642 | } |
2582 | | - return emails; |
2583 | 2643 | } |
| 2644 | + return emails; |
| 2645 | + } |
2584 | 2646 |
|
2585 | | - // in case no pre-defined attribute was found, use all configured roles with specified preferred email |
| 2647 | + /** |
| 2648 | + * Get "TO" emails from notification receiver roles of the specified object. |
| 2649 | + * @param object String specification of the object |
| 2650 | + * @param objectInstance instance of the object |
| 2651 | + * @return Set of identified emails. Can be empty if not roles or users in the role have been identified, |
| 2652 | + * or they do not have email address set. |
| 2653 | + * @throws WrongAttributeAssignmentException |
| 2654 | + * @throws AttributeNotExistsException |
| 2655 | + */ |
| 2656 | + private Set<String> getToEmailsViaRoles(String object, PerunBean objectInstance) |
| 2657 | + throws WrongAttributeAssignmentException, AttributeNotExistsException |
| 2658 | + { |
| 2659 | + Set<String> emails = new HashSet<>(); |
2586 | 2660 | try { |
2587 | | - List<String> rolesToNotify = getNotificationReceiverRoles(VO); |
| 2661 | + List<String> rolesToNotify = getNotificationReceiverRoles(object); |
2588 | 2662 | List<RichUser> admins = new ArrayList<>(); |
2589 | 2663 | for (String role : rolesToNotify) { |
2590 | | - admins.addAll(AuthzResolverBlImpl.getRichAdmins(registrarSession, app.getVo(), role)); |
| 2664 | + admins.addAll(AuthzResolverBlImpl.getRichAdmins(registrarSession, objectInstance, role)); |
2591 | 2665 | } |
2592 | 2666 | emails.addAll(getUserPreferredMails(registrarSession, admins)); |
2593 | | - emails = emails.stream().distinct().toList(); |
| 2667 | + return emails; |
2594 | 2668 | } catch (RoleCannotBeManagedException e) { |
2595 | 2669 | throw new InternalErrorException(e); |
2596 | 2670 | } |
2597 | | - |
2598 | | - return emails; |
2599 | 2671 | } |
2600 | 2672 |
|
2601 | 2673 | /** |
|
0 commit comments