Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IQSS/9185 contact email updates #9186

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9d29441
initial cc in contact form email
qqmyers Nov 22, 2022
666fdc3
add support email setting
qqmyers Nov 22, 2022
67ecb2d
minimal feedback API method
qqmyers Nov 22, 2022
4fac803
handle null cc
qqmyers Nov 22, 2022
e0b7f6f
more comment
qqmyers Nov 22, 2022
4973498
docs, release notes
qqmyers Nov 23, 2022
47397c1
add PersonOrOrgUtil from #9089
qqmyers Nov 23, 2022
0c9c765
fix text
qqmyers Nov 23, 2022
7b7d9f5
typo
qqmyers Nov 23, 2022
5fd7fc1
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Feb 7, 2023
4d36137
Merge remote-tracking branch 'IQSS/develop' into IQSS/9185-contact_em…
qqmyers Feb 14, 2023
9092a5a
Merge remote-tracking branch 'IQSS/develop' into IQSS/9185-contact_em…
qqmyers Mar 3, 2023
eb0a446
Merge remote-tracking branch 'IQSS/develop' into
qqmyers Mar 22, 2023
c982ec7
Merge remote-tracking branch 'IQSS/develop' into IQSS/9185-contact_em…
qqmyers Apr 24, 2023
609d958
changes per review request
qqmyers Apr 26, 2023
6a5c9f9
update release note
qqmyers Apr 26, 2023
0ddd6a1
fix compile issues/cleanup
qqmyers Apr 26, 2023
bb7818c
Remove commented out methods and unused code per review
qqmyers Apr 28, 2023
6251f7f
remove null param
qqmyers Apr 28, 2023
8c0ccea
and in FeedbackApi
qqmyers Apr 28, 2023
9d8132a
fixes/adds per QA
qqmyers May 4, 2023
42b9ff0
add note about support email needing to be a valid from address
qqmyers May 4, 2023
6de83a6
remove bad curl example
qqmyers May 4, 2023
7f1d469
not plural
qqmyers May 4, 2023
9b99535
fix missing method
qqmyers May 5, 2023
5727d7f
support both
qqmyers May 5, 2023
fb30dbf
only show cc when used
qqmyers May 5, 2023
efd5ac6
doc updates per QA
qqmyers May 5, 2023
7e0b0b3
fix to not replyto/from
qqmyers May 5, 2023
82c129f
fix config docs
qqmyers May 5, 2023
cd2f196
tweak api language
qqmyers May 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/release-notes/9185-contact-email-updates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Contact Email Improvements

Email sent from the contact forms to the contact(s) for a collection, dataset, or datafile can now optionally be cc'd to a support email address. The support email address can be changed from the default :SystemEmail address to a separate :SupportEmail address. When multiple contacts are listed, the system will now send one email to all contacts (with the optional cc if configured) instead of separate emails to each contact. Contact names with a comma that refer to Organizations will no longer have the name parts reversed in the email greeting. A new protected feedback API has been added.

## Backward Incompatibilities

When there are multiple contacts, the system will now send one email with all of the contacts in the To: header instead of sending one email to each contact (with no indication that others have been notified).

## New JVM/MicroProfile Settings

dataverse.mail.support-email - allows a separate email, distinct from the :SystemEmail to be used as the to address in emails from the contact form/ feedback api.
dataverse.mail.cc-support-on-contact-emails - include the support email address as a CC: entry when contact/feedback emails are sent to the contacts for a collection, dataset, or datafile.
30 changes: 30 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1618,6 +1618,8 @@ The fully expanded example above (without environment variables) looks like this

The people who need to review the dataset (often curators or journal editors) can check their notifications periodically via API to see if any new datasets have been submitted for review and need their attention. See the :ref:`Notifications` section for details. Alternatively, these curators can simply check their email or notifications to know when datasets have been submitted (or resubmitted) for review.

.. _return-a-dataset:

Return a Dataset to Author
~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1645,6 +1647,8 @@ The fully expanded example above (without environment variables) looks like this

The review process can sometimes resemble a tennis match, with the authors submitting and resubmitting the dataset over and over until the curators are satisfied. Each time the curators send a "reason for return" via API, that reason is persisted into the database, stored at the dataset version level.

The :ref:`send-feedback` API call may be useful as a way to move the conversation to email. However, note that these emails go to contacts (versus authors) and there is no database record of the email contents. (:ref:`dataverse.mail.cc-support-on-contact-email` will send a copy of these emails to the support email address which would provide a record.)

Link a Dataset
~~~~~~~~~~~~~~

Expand Down Expand Up @@ -4497,3 +4501,29 @@ A curl example using allowing access to a dataset's metadata

Please see :ref:`dataverse.api.signature-secret` for the configuration option to add a shared secret, enabling extra
security.

.. _send-feedback:

Send Feedback To Contact(s)
~~~~~~~~~~~~~~~~~~~~~~~~~~~

qqmyers marked this conversation as resolved.
Show resolved Hide resolved
This API call allows sending an email to the contacts for a collection, dataset, or datafile or to the support email address when no object is specified.
The call is protected by the normal /admin API protections (limited to localhost or requiring a separate key), but does not otherwise limit the sending of emails.
Administrators should be sure only trusted applications have access to avoid the potential for spam.

The call is a POST with a JSON object as input with four keys:
- "targetId" - the id of the collection, dataset, or datafile. Persistent ids and collection aliases are not supported. (Optional)
- "subject" - the email subject line
- "body" - the email body to send
- "fromEmail" - the email to list in the reply-to field. (Dataverse always sends mail from the system email, but does it "on behalf of" and with a reply-to for the specified user.)

A curl example using an ``ID``

.. code-block:: bash

export SERVER_URL=http://localhost
export JSON='{"targetId":24, "subject":"Data Question", "body":"Please help me understand your data. Thank you!", "fromEmail":"dataverseSupport@mailinator.com"}'

curl -X POST -H 'Content-Type:application/json' -d "$JSON" $SERVER_URL/api/admin/feedback

Note that this call could be useful in coordinating with dataset authors (assuming they are also contacts) as an alternative/addition to the functionality provided by :ref:`return-a-dataset`.
22 changes: 22 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,27 @@ See :ref:`discovery-sign-posting` for details.

Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_SIGNPOSTING_LEVEL1_ITEM_LIMIT``.

dataverse.mail.support-email
++++++++++++++++++++++++++++

This provides an email address distinct from the :ref:`systemEmail` that will be used as the email address for Contact Forms and Feedback API. This address is used as the To address when the Contact form is launched from the Support entry in the top navigation bar and, if configured via :ref:`dataverse.mail.cc-support-on-contact-email`, as a CC address when the form is launched from a Dataverse/Dataset Contact button.
This allows configuration of a no-reply email address for :ref:`systemEmail` while allowing feedback to go to/be cc'd to the support email address, which would normally accept replies. If not set, the :ref:`systemEmail` is used for the feedback API/contact form email.

Note that only the email address is required, which you can supply without the ``<`` and ``>`` signs, but if you include the text, it's the way to customize the name of your support team, which appears in the "from" address in emails as well as in help text in the UI. If you don't include the text, the installation name (see :ref:`Branding Your Installation`) will appear in the "from" address.

Can also be set via any `supported MicroProfile Config API source`_, e.g. the environment variable ``DATAVERSE_MAIL_SUPPORT_EMAIL``.

.. _dataverse.mail.cc-support-on-contact-email:

dataverse.mail.cc-support-on-contact-email
++++++++++++++++++++++++++++++++++++++++++

If this setting is true, the contact forms and feedback API will cc the system (:SupportEmail if set, :SystemEmail if not) when sending email to the collection, dataset, or datafile contacts.
A CC line is added to the contact form when this setting is true so that users are aware that the cc will occur.
The default is false.

Can also be set via *MicroProfile Config API* sources, e.g. the environment variable ``DATAVERSE_MAIL_CC_SUPPORT_ON_CONTACT_EMAIL``.


.. _feature-flags:

Expand Down Expand Up @@ -3812,3 +3833,4 @@ To use the current GDCC version directly:
``curl -X PUT -d 'https://gdcc.github.io/dvwebloader/src/dvwebloader.html' http://localhost:8080/api/admin/settings/:WebloaderUrl``

.. _supported MicroProfile Config API source: https://docs.payara.fish/community/docs/Technical%20Documentation/MicroProfile/Config/Overview.html

49 changes: 4 additions & 45 deletions src/main/java/edu/harvard/iq/dataverse/MailServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,37 +81,6 @@ public class MailServiceBean implements java.io.Serializable {
public MailServiceBean() {
}

public void sendMail(String host, String reply, String to, String subject, String messageText) {
Properties props = System.getProperties();
props.put("mail.smtp.host", host);
Session session = Session.getDefaultInstance(props, null);

try {
MimeMessage msg = new MimeMessage(session);
String[] recipientStrings = to.split(",");
InternetAddress[] recipients = new InternetAddress[recipientStrings.length];
try {
InternetAddress fromAddress = getSystemAddress();
setContactDelegation(reply, fromAddress);
msg.setFrom(fromAddress);
msg.setReplyTo(new Address[] {new InternetAddress(reply, charset)});
for (int i = 0; i < recipients.length; i++) {
recipients[i] = new InternetAddress(recipientStrings[i], "", charset);
}
} catch (UnsupportedEncodingException ex) {
logger.severe(ex.getMessage());
}
msg.setRecipients(Message.RecipientType.TO, recipients);
msg.setSubject(subject, charset);
msg.setText(messageText, charset);
Transport.send(msg, recipients);
} catch (AddressException ae) {
ae.printStackTrace(System.out);
} catch (MessagingException me) {
me.printStackTrace(System.out);
}
}

@Resource(name = "mail/notifyMailSession")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we need to keep this fully commented out method around? (Also down around line 180)

private Session session;

Expand Down Expand Up @@ -177,11 +146,7 @@ public InternetAddress getSystemAddress() {
}

//@Resource(name="mail/notifyMailSession")
public void sendMail(String from, String to, String subject, String messageText) {
sendMail(from, to, subject, messageText, new HashMap<>());
}

public void sendMail(String reply, String to, String subject, String messageText, Map<Object, Object> extraHeaders) {
public void sendMail(String reply, String to, String cc, String subject, String messageText) {
try {
qqmyers marked this conversation as resolved.
Show resolved Hide resolved
MimeMessage msg = new MimeMessage(session);
// Always send from system address to avoid email being blocked
Expand All @@ -202,18 +167,12 @@ public void sendMail(String reply, String to, String subject, String messageText
msg.setSentDate(new Date());
msg.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to, false));
if (cc != null) {
msg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc, false));
}
msg.setSubject(subject, charset);
msg.setText(messageText, charset);

if (extraHeaders != null) {
for (Object key : extraHeaders.keySet()) {
String headerName = key.toString();
String headerValue = extraHeaders.get(key).toString();

msg.addHeader(headerName, headerValue);
}
}

Transport.send(msg);
} catch (AddressException ae) {
ae.printStackTrace(System.out);
Expand Down
55 changes: 37 additions & 18 deletions src/main/java/edu/harvard/iq/dataverse/SendFeedbackDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import edu.harvard.iq.dataverse.branding.BrandingUtil;
import edu.harvard.iq.dataverse.feedback.Feedback;
import edu.harvard.iq.dataverse.feedback.FeedbackUtil;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.MailUtil;
import edu.harvard.iq.dataverse.util.SystemConfig;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.logging.Logger;
import javax.ejb.EJB;
Expand Down Expand Up @@ -62,7 +63,7 @@ public class SendFeedbackDialog implements java.io.Serializable {
* Either the dataverse or the dataset that the message is pertaining to. If
* there is no recipient, this is a general feedback message.
*/
private DvObject recipient;
private DvObject feedbackTarget;

/**
* :SystemEmail (the main support address for an installation).
Expand Down Expand Up @@ -97,11 +98,11 @@ public void initUserInput(ActionEvent ae) {
userMessage = "";
messageSubject = "";
Random random = new Random();
op1 = new Long(random.nextInt(10));
op2 = new Long(random.nextInt(10));
op1 = Long.valueOf(random.nextInt(10));
op2 = Long.valueOf(random.nextInt(10));
userSum = null;
String systemEmail = settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail);
systemAddress = MailUtil.parseSystemAddress(systemEmail);
String supportEmail = JvmSettings.SUPPORT_EMAIL.lookupOptional().orElse(settingsService.getValueForKey(SettingsServiceBean.Key.SystemEmail));
systemAddress = MailUtil.parseSystemAddress(supportEmail);
}

public Long getOp1() {
Expand Down Expand Up @@ -129,19 +130,27 @@ public void setUserSum(Long userSum) {
}

public String getMessageTo() {
if (recipient == null) {
if (feedbackTarget == null) {
return BrandingUtil.getSupportTeamName(systemAddress);
} else if (recipient.isInstanceofDataverse()) {
return ((Dataverse) recipient).getDisplayName() + " " + BundleUtil.getStringFromBundle("contact.contact");
} else if (feedbackTarget.isInstanceofDataverse()) {
return ((Dataverse) feedbackTarget).getDisplayName() + " " + BundleUtil.getStringFromBundle("contact.contact");
} else {
return BundleUtil.getStringFromBundle("dataset") + " " + BundleUtil.getStringFromBundle("contact.contact");
}
}

public String getMessageCC() {
if (ccSupport()) {
return BrandingUtil.getSupportTeamName(systemAddress);
}
return null;
}


public String getFormHeader() {
if (recipient == null) {
if (feedbackTarget == null) {
return BrandingUtil.getContactHeader(systemAddress);
} else if (recipient.isInstanceofDataverse()) {
} else if (feedbackTarget.isInstanceofDataverse()) {
return BundleUtil.getStringFromBundle("contact.dataverse.header");
} else {
return BundleUtil.getStringFromBundle("contact.dataset.header");
Expand Down Expand Up @@ -173,11 +182,11 @@ public String loggedInUserEmail() {
}

public DvObject getRecipient() {
return recipient;
return feedbackTarget;
}

public void setRecipient(DvObject recipient) {
this.recipient = recipient;
this.feedbackTarget = recipient;
}

public void validateUserSum(FacesContext context, UIComponent component, Object value) throws ValidatorException {
Expand All @@ -200,16 +209,26 @@ public void validateUserEmail(FacesContext context, UIComponent component, Objec
public String sendMessage() {
String installationBrandName = BrandingUtil.getInstallationBrandName();
String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress);
List<Feedback> feedbacks = FeedbackUtil.gatherFeedback(recipient, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName);
if (feedbacks.isEmpty()) {

Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, systemConfig.getDataverseSiteUrl(), installationBrandName, supportTeamName, ccSupport());
if (feedback==null) {
logger.warning("No feedback has been sent!");
return null;
}
for (Feedback feedback : feedbacks) {
logger.fine("sending feedback: " + feedback);
mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getSubject(), feedback.getBody());
}
mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody());
return null;
}

public boolean ccSupport() {
return ccSupport(feedbackTarget);
}

public static boolean ccSupport(DvObject feedbackTarget) {
//Setting is enabled and this isn't already a direct message to support (no feedbackTarget)
Optional<Boolean> ccSupport = JvmSettings.CC_SUPPORT_ON_CONTACT_EMAIL.lookupOptional(Boolean.class);

return feedbackTarget!=null && ccSupport.isPresent() &&ccSupport.get();
}

}
45 changes: 31 additions & 14 deletions src/main/java/edu/harvard/iq/dataverse/api/FeedbackApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

import edu.harvard.iq.dataverse.DataverseSession;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.DvObjectServiceBean;
import edu.harvard.iq.dataverse.MailServiceBean;
import edu.harvard.iq.dataverse.SendFeedbackDialog;
import edu.harvard.iq.dataverse.branding.BrandingUtil;
import edu.harvard.iq.dataverse.feedback.Feedback;
import edu.harvard.iq.dataverse.feedback.FeedbackUtil;
import java.util.List;
import edu.harvard.iq.dataverse.settings.JvmSettings;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.util.MailUtil;

import javax.ejb.EJB;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
Expand All @@ -17,34 +21,47 @@
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

@Path("admin/feedback")
public class FeedbackApi extends AbstractApiBean {

@EJB
DvObjectServiceBean dvObjectSvc;

@EJB MailServiceBean mailService;

/**
* This method mimics the contact form and sends an email to the contacts of the
* specified Collection/Dataset/DataFile, optionally ccing the support email
* address, or to the support email address when there is no target object.
*
* !!!!! This should not be moved outside the /admin path unless/until some form
* of captcha or other spam-prevention mechanism is added. As is, it allows an
* unauthenticated user (with access to the /admin api path) to send email from
* anyone to any contacts in Dataverse. (It also does not do much to validate
* user input (e.g. to strip potentially malicious html, etc.)!!!!
**/
@POST
public Response submitFeedback(JsonObject jsonObject) throws AddressException {
JsonNumber jsonNumber = jsonObject.getJsonNumber("id");
DvObject recipient = null;
JsonNumber jsonNumber = jsonObject.getJsonNumber("targetId");
DvObject feedbackTarget = null;
if (jsonNumber != null) {
recipient = dvObjectSvc.findDvObject(jsonNumber.longValue());
feedbackTarget = dvObjSvc.findDvObject(jsonNumber.longValue());
if(feedbackTarget==null) {
return error(Status.BAD_REQUEST, "Feedback target object not found");
}
}
DataverseSession dataverseSession = null;
String userMessage = jsonObject.getString("body");
String systemEmail = "support@librascholar.edu";
InternetAddress systemAddress = new InternetAddress(systemEmail);
String systemEmail = JvmSettings.SUPPORT_EMAIL.lookupOptional().orElse(settingsSvc.getValueForKey(SettingsServiceBean.Key.SystemEmail));
InternetAddress systemAddress = MailUtil.parseSystemAddress(systemEmail);
String userEmail = jsonObject.getString("fromEmail");
String messageSubject = jsonObject.getString("subject");
String baseUrl = systemConfig.getDataverseSiteUrl();
String installationBrandName = BrandingUtil.getInstallationBrandName();
String supportTeamName = BrandingUtil.getSupportTeamName(systemAddress);
JsonArrayBuilder jab = Json.createArrayBuilder();
List<Feedback> feedbacks = FeedbackUtil.gatherFeedback(recipient, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName);
feedbacks.forEach((feedback) -> {
jab.add(feedback.toJsonObjectBuilder());
});
Feedback feedback = FeedbackUtil.gatherFeedback(feedbackTarget, dataverseSession, messageSubject, userMessage, systemAddress, userEmail, baseUrl, installationBrandName, supportTeamName, SendFeedbackDialog.ccSupport(feedbackTarget));
jab.add(feedback.toJsonObjectBuilder());
mailService.sendMail(feedback.getFromEmail(), feedback.getToEmail(), feedback.getCcEmail(), feedback.getSubject(), feedback.getBody());
return ok(jab);
}
}
Loading