/*
* Copyright 2012 SURFnet bv, The Netherlands
*
* 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 teams.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import teams.domain.Invitation;
import teams.domain.Member;
import teams.domain.Person;
import teams.domain.Role;
import teams.domain.Team;
import teams.service.GrouperTeamService;
import teams.service.mail.MailService;
/**
* This class includes methods that are often used by controllers
*/
@Component("controllerUtil")
public class ControllerUtilImpl implements ControllerUtil {
private static final Logger log = LoggerFactory.getLogger(ControllerUtilImpl.class);
private static final String REQUEST_MEMBERSHIP_SUBJECT = "request.MembershipSubject";
@Autowired
private GrouperTeamService grouperTeamService;
@Autowired
private Configuration freemarkerConfiguration;
@Autowired
private MailService mailService;
@Value("${systemEmail}")
private String systemEmail;
@Autowired
private MessageSource messageSource;
@Value("${teamsURL}")
private String teamsUrl;
/**
* Get the team from the {@link HttpServletRequest} request.
*
* @param request the {@link HttpServletRequest}
* @return The {@link Team} team
* @throws RuntimeException if the team cannot be found
*/
public Team getTeam(HttpServletRequest request) {
return getTeamById(request.getParameter("team"));
}
/**
* Get the team from the {@link String} teamId.
*
* @param teamId the {@link String} teamId
* @return The {@link Team} team
* @throws RuntimeException if the team cannot be found
*/
public Team getTeamById(String teamId) {
checkArgument(StringUtils.hasText(teamId));
return grouperTeamService.findTeamById(teamId);
}
@Override
public boolean hasUserAdministrativePrivileges(Person person, Team team) {
// Check if the requester is member of the team AND
// Check if the requester has the role admin or manager, so he is allowed to invite new members.
Member member = grouperTeamService.findMember(team, person.getId());
return member != null && (member.getRoles().contains(Role.Admin) || member.getRoles().contains(Role.Manager));
}
@Override
public boolean hasUserAdminPrivileges(Person person, Team team) {
// Check if the requester is member of the team AND
// Check if the requester has the role admin or manager, so he is allowed to invite new members.
Member member = grouperTeamService.findMember(team, person.getId());
return member != null && (member.getRoles().contains(Role.Admin));
}
public boolean isPersonMemberOfTeam(Person person, Team team) {
return team.getMembers().stream().anyMatch(m -> m.getId().equals(person.getId()));
}
private MimeMultipart getMimeMultipartMessageBody(String plainText, String html) throws MessagingException {
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(plainText, "utf-8");
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(html, "text/html; charset=utf-8");
MimeMultipart multiPart = new MimeMultipart("alternative");
multiPart.addBodyPart(textPart); // least important
multiPart.addBodyPart(htmlPart); // most important
return multiPart;
}
@Override
public void sendInvitationMail(Team team, Invitation invitation, String subject, Person inviter) {
String html = composeInvitationMailMessage(team, invitation, inviter, "html");
String plainText = composeInvitationMailMessage(team, invitation, inviter, "plaintext");
MimeMessagePreparator preparator = mimeMessage -> {
mimeMessage.addHeader("Precedence", "bulk");
mimeMessage.setRecipient(Message.RecipientType.TO, new InternetAddress(invitation.getEmail()));
mimeMessage.setFrom(new InternetAddress(systemEmail));
mimeMessage.setSubject(subject);
MimeMultipart rootMixedMultipart = getMimeMultipartMessageBody(plainText, html);
mimeMessage.setContent(rootMixedMultipart);
};
mailService.sendAsync(preparator);
}
private String composeInvitationMailMessage(Team team, Invitation invitation, Person inviter, String variant) {
String templateName = "plaintext".equals(variant) ? "invitationmail-plaintext.ftl" : "invitationmail.ftl";
Map<String, Object> templateVars = new HashMap<>();
templateVars.put("invitation", invitation);
templateVars.put("inviter", inviter);
templateVars.put("team", team);
templateVars.put("teamsURL", teamsUrl);
templateVars.put("messages", messageSource);
templateVars.put("locale", invitation.getLanguage().locale());
try {
Template template = freemarkerConfiguration.getTemplate(templateName, invitation.getLanguage().locale());
return FreeMarkerTemplateUtils.processTemplateIntoString(template, templateVars);
} catch (IOException | TemplateException e) {
throw new RuntimeException("Failed to create invitation mail", e);
}
}
@Override
public void sendAcceptMail(Person memberToAdd, Team team, Locale locale) {
String subject = messageSource.getMessage("request.mail.accepted.subject", null, locale);
String html = composeAcceptMailMessage(team, locale, "html");
String plainText = composeAcceptMailMessage(team, locale, "plaintext");
MimeMessagePreparator preparator = mimeMessage -> {
mimeMessage.addHeader("Precedence", "bulk");
mimeMessage.setFrom(new InternetAddress(systemEmail));
mimeMessage.setRecipients(Message.RecipientType.TO, new Address[]{new InternetAddress(memberToAdd.getEmail())});
mimeMessage.setSubject(subject);
MimeMultipart rootMixedMultipart = getMimeMultipartMessageBody(plainText, html);
mimeMessage.setContent(rootMixedMultipart);
};
mailService.sendAsync(preparator);
}
private String composeAcceptMailMessage(Team team, Locale locale, String variant) {
String templateName = "plaintext".equals(variant) ? "joinrequest-acceptmail-plaintext.ftl" : "joinrequest-acceptmail.ftl";
Map<String, Object> templateVars = new HashMap<>();
templateVars.put("team", team);
try {
return FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerConfiguration.getTemplate(templateName, locale), templateVars);
} catch (IOException e) {
throw new RuntimeException("Failed to create accept join request mail", e);
} catch (TemplateException e) {
throw new RuntimeException("Failed to create accept join request mail", e);
}
}
@Override
public void sendDeclineMail(Person memberToAdd, Team team, Locale locale) {
checkArgument(!isNullOrEmpty(memberToAdd.getEmail()));
String subject = messageSource.getMessage("request.mail.declined.subject", null, locale);
String html = composeDeclineMailMessage(team, locale, "html");
String plainText = composeDeclineMailMessage(team, locale, "plaintext");
MimeMessagePreparator preparator = mimeMessage -> {
mimeMessage.addHeader("Precedence", "bulk");
mimeMessage.setFrom(new InternetAddress(systemEmail));
mimeMessage.setRecipients(Message.RecipientType.TO, new Address[] { new InternetAddress(memberToAdd.getEmail()) } );
mimeMessage.setSubject(subject);
MimeMultipart rootMixedMultipart = getMimeMultipartMessageBody(plainText, html);
mimeMessage.setContent(rootMixedMultipart);
};
mailService.sendAsync(preparator);
}
private String composeDeclineMailMessage(Team team, Locale locale, String variant) {
String templateName = "plaintext".equals(variant) ? "joinrequest-declinemail-plaintext.ftl" : "joinrequest-declinemail.ftl";
Map<String, Object> templateVars = new HashMap<>();
templateVars.put("team", team);
try {
return FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerConfiguration.getTemplate(templateName, locale), templateVars);
} catch (IOException e) {
throw new RuntimeException("Failed to create decline join request mail", e);
} catch (TemplateException e) {
throw new RuntimeException("Failed to create decline join request mail", e);
}
}
@Override
public void sendJoinTeamMail(Team team, Person person, String message, Locale locale) {
Object[] subjectValues = {team.getName()};
String subject = messageSource.getMessage(REQUEST_MEMBERSHIP_SUBJECT, subjectValues, locale);
Set<Member> admins = grouperTeamService.findAdmins(team);
if (CollectionUtils.isEmpty(admins)) {
throw new RuntimeException("Team '" + team.getName() + "' has no admins to mail invites");
}
String html = composeJoinRequestMailMessage(team, person, message, locale, "html");
String plainText = composeJoinRequestMailMessage(team, person, message, locale, "plaintext");
List<InternetAddress> bcc = new ArrayList<>();
for (Member admin : admins) {
try {
bcc.add(new InternetAddress(admin.getEmail()));
} catch (AddressException ae) {
log.debug("Admin has malformed email address", ae);
}
}
if (bcc.isEmpty()) {
throw new RuntimeException("Team '" + team.getName() + "' has no admins with valid email addresses to mail invites");
}
MimeMessagePreparator preparator = mimeMessage -> {
mimeMessage.addHeader("Precedence", "bulk");
mimeMessage.setFrom(new InternetAddress(systemEmail));
mimeMessage.setRecipients(Message.RecipientType.BCC, bcc.toArray(new InternetAddress[bcc.size()]));
mimeMessage.setSubject(subject);
MimeMultipart rootMixedMultipart = getMimeMultipartMessageBody(plainText, html);
mimeMessage.setContent(rootMixedMultipart);
};
mailService.sendAsync(preparator);
}
private String composeJoinRequestMailMessage(Team team, Person person, String message, Locale locale, String variant) {
String templateName = "plaintext".equals(variant) ? "joinrequestmail-plaintext.ftl" : "joinrequestmail.ftl";
Map<String, Object> templateVars = new HashMap<>();
templateVars.put("requesterName", person.getDisplayName());
// for unknown reasons Freemarker cannot call person.getEmail()
templateVars.put("requesterEmail", person.getEmail());
templateVars.put("team", team);
templateVars.put("teamsURL", teamsUrl);
templateVars.put("message", message);
try {
return FreeMarkerTemplateUtils.processTemplateIntoString(freemarkerConfiguration.getTemplate(templateName, locale), templateVars);
} catch (IOException e) {
throw new RuntimeException("Failed to create invitation mail", e);
} catch (TemplateException e) {
throw new RuntimeException("Failed to create invitation mail", e);
}
}
}