package org.molgenis.ui.controller;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.molgenis.auth.User;
import org.molgenis.data.settings.AppSettings;
import org.molgenis.security.captcha.CaptchaException;
import org.molgenis.security.captcha.CaptchaRequest;
import org.molgenis.security.captcha.CaptchaService;
import org.molgenis.security.core.utils.SecurityUtils;
import org.molgenis.security.user.UserService;
import org.molgenis.ui.MolgenisPluginController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailSendException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
/**
* Controller that handles feedback page requests. The user can fill in this feedback form to send a mail to the app's
* administrators.
*/
@Controller
@RequestMapping(FeedbackController.URI)
public class FeedbackController extends AbstractStaticContentController
{
private static final Logger LOG = LoggerFactory.getLogger(FeedbackController.class);
public static final String ID = "feedback";
public static final String URI = MolgenisPluginController.PLUGIN_URI_PREFIX + ID;
private static final String MESSAGING_EXCEPTION_MESSAGE = "Unfortunately, we were unable to create an email message for the feedback you specified.";
private static final String MAIL_AUTHENTICATION_EXCEPTION_MESSAGE = "Unfortunately, we were unable to send the mail containing your feedback. Please contact the administrator.";
private static final String MAIL_SEND_EXCEPTION_MESSAGE = MAIL_AUTHENTICATION_EXCEPTION_MESSAGE;
private final UserService userService;
private final AppSettings appSettings;
private final CaptchaService captchaService;
private final JavaMailSender mailSender;
@Autowired
public FeedbackController(UserService userService, AppSettings appSettings,
CaptchaService captchaService, JavaMailSender mailSender)
{
super(ID, URI);
this.userService = requireNonNull(userService);
this.appSettings = requireNonNull(appSettings);
this.captchaService = requireNonNull(captchaService);
this.mailSender = requireNonNull(mailSender);
}
/**
* Serves feedback form.
*/
@Override
@RequestMapping(method = RequestMethod.GET)
public String init(final Model model)
{
super.init(model);
model.addAttribute("adminEmails", userService.getSuEmailAddresses());
if (SecurityUtils.currentUserIsAuthenticated())
{
User currentUser = userService.getUser(SecurityUtils.getCurrentUsername());
model.addAttribute("userName", getFormattedName(currentUser));
model.addAttribute("userEmail", currentUser.getEmail());
}
return "view-feedback";
}
/**
* Handles feedback form submission.
*
* @throws CaptchaException if no valid captcha is supplied
*/
@RequestMapping(method = RequestMethod.POST)
public String submitFeedback(@Valid FeedbackForm form, @Valid @ModelAttribute CaptchaRequest captchaRequest)
throws CaptchaException
{
if (!captchaService.validateCaptcha(captchaRequest.getCaptcha()))
{
form.setErrorMessage("Invalid captcha.");
return "view-feedback";
}
try
{
LOG.info("Sending feedback:" + form);
MimeMessage message = createFeedbackMessage(form);
mailSender.send(message);
form.setSubmitted(true);
captchaService.removeCaptcha();
}
catch (MessagingException e)
{
LOG.warn("Unable to create mime message for feedback form.", e);
form.setErrorMessage(MESSAGING_EXCEPTION_MESSAGE);
}
catch (MailAuthenticationException e)
{
LOG.error("Error authenticating with email server.", e);
form.setErrorMessage(MAIL_AUTHENTICATION_EXCEPTION_MESSAGE);
}
catch (MailSendException e)
{
LOG.error("Error sending mail", e);
form.setErrorMessage(MAIL_SEND_EXCEPTION_MESSAGE);
}
return "view-feedback";
}
/**
* Creates a MimeMessage based on a FeedbackForm.
*/
private MimeMessage createFeedbackMessage(FeedbackForm form) throws MessagingException
{
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, false);
helper.setTo(userService.getSuEmailAddresses().toArray(new String[0]));
if (form.hasEmail())
{
helper.setCc(form.getEmail());
helper.setReplyTo(form.getEmail());
}
else
{
helper.setReplyTo("no-reply@molgenis.org");
}
String appName = appSettings.getTitle();
helper.setSubject(String.format("[feedback-%s] %s", appName, form.getSubject()));
helper.setText(String.format("Feedback from %s:\n\n%s", form.getFrom(), form.getFeedback()));
return message;
}
/**
* Formats a MolgenisUser's name.
*
* @return String containing the user's first name, middle names and last name.
*/
private static String getFormattedName(User user)
{
List<String> parts = new ArrayList<String>();
if (user.getTitle() != null)
{
parts.add(user.getTitle());
}
if (user.getFirstName() != null)
{
parts.add(user.getFirstName());
}
if (user.getMiddleNames() != null)
{
parts.add(user.getMiddleNames());
}
if (user.getLastName() != null)
{
parts.add(user.getLastName());
}
if (parts.isEmpty())
{
return null;
}
else
{
return StringUtils.collectionToDelimitedString(parts, " ");
}
}
/**
* Bean for the feedback form data.
*/
public static class FeedbackForm
{
private String name;
private String email;
private String subject;
private String feedback;
private boolean submitted = false;
private String errorMessage;
public String getName()
{
return name;
}
public boolean hasName()
{
return name != null && !name.trim().isEmpty();
}
public void setName(String name)
{
this.name = name;
}
@Email
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public boolean hasEmail()
{
return email != null && !email.trim().isEmpty();
}
public String getFrom()
{
StringBuilder result = new StringBuilder();
if (!hasName() && !hasEmail())
{
result.append("Anonymous");
}
else
{
if (hasName())
{
result.append(getName());
}
if (hasEmail())
{
if (hasName())
{
result.append(String.format(" (%s)", getEmail()));
}
else
{
result.append(getEmail());
}
}
}
return result.toString();
}
public String getSubject()
{
if (subject == null || subject.trim().isEmpty())
{
return "<no subject>";
}
return subject;
}
public void setSubject(String subject)
{
this.subject = subject;
}
@NotBlank
public String getFeedback()
{
return feedback;
}
public void setFeedback(String feedback)
{
this.feedback = feedback;
}
public void setSubmitted(boolean b)
{
this.submitted = b;
}
public boolean isSubmitted()
{
return submitted;
}
public String getErrorMessage()
{
return errorMessage;
}
public void setErrorMessage(String errorMessage)
{
this.errorMessage = errorMessage;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("[From: ");
builder.append(getFrom());
builder.append("\nSubject: ");
builder.append(getSubject());
builder.append("\nBody: ");
builder.append(getFeedback());
builder.append(']');
return builder.toString();
}
}
}