Skip to content

Commit

Permalink
Added configurable admin notification.
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanmrsulja committed Dec 25, 2023
1 parent 8c92761 commit c2656a4
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;

Expand Down Expand Up @@ -47,20 +48,34 @@ public class ForgotPasswordController extends FreemarkerHttpServlet {

@Override
protected ResponseValues processRequest(VitroRequest vreq) throws Exception {
// Random time interval sleep so attacker can't calculate whether provided email is bound or not
// Random time interval sleep so attacker can't calculate whether provided email is bound to an account or not
sleepForRandomTime();

Map<String, Object> dataContext = new HashMap<>();
setCommonValues(dataContext, vreq);
UserAccountsDao userAccountsDao = constructUserAccountsDao(vreq);
I18nBundle i18n = I18n.bundle(vreq);

boolean isEnabled = isFunctionalityEnabled(vreq);
boolean isEnabled = isFunctionalityEnabled();
dataContext.put("isEnabled", isEnabled);

// Handle GET request (display the form) or print error message if functionality is disabled
if (!isEnabled || vreq.getMethod().equalsIgnoreCase("GET")) {
return showForm(dataContext);
}

return handlePostRequest(vreq, dataContext);
}

/**
* Handles a POST request for password reset. Validates the captcha, checks for spam mitigation,
* processes the password change request, and notifies the user.
*
* @param vreq The Vitro request object.
* @param dataContext A map containing additional data context for processing the request.
* @return A response containing the appropriate values for rendering the result.
*/
private ResponseValues handlePostRequest(VitroRequest vreq, Map<String, Object> dataContext) {
UserAccountsDao userAccountsDao = constructUserAccountsDao(vreq);
I18nBundle i18n = I18n.bundle(vreq);

String captchaInput = vreq.getParameter("defaultReal");
String captchaDisplay = vreq.getParameter("defaultRealHash");
if (!captchaHash(captchaInput).equals(captchaDisplay)) {
Expand Down Expand Up @@ -100,10 +115,8 @@ protected ResponseValues processRequest(VitroRequest vreq) throws Exception {
* @param i18n The internationalization bundle for language translation.
* @param vreq The VitroRequest object containing request information.
*/
private void notifyUser(UserAccount userAccount, I18nBundle i18n,
VitroRequest vreq) {

Map<String, Object> body = new HashMap<String, Object>();
private void notifyUser(UserAccount userAccount, I18nBundle i18n, VitroRequest vreq) {
Map<String, Object> body = new HashMap<>();
body.put("userAccount", userAccount);
body.put("passwordLink",
buildResetPasswordLink(userAccount.getEmailAddress(), userAccount.getEmailKey(), vreq));
Expand All @@ -112,14 +125,62 @@ private void notifyUser(UserAccount userAccount, I18nBundle i18n,
body.put("textMessage", i18n.text("password_reset_pending_email_plain_text"));
body.put("htmlMessage", i18n.text("password_reset_pending_email_html_text"));

sendEmailMessage(body, vreq, userAccount.getEmailAddress());

if (adminShouldBeNotified()) {
notifyAdmin(userAccount, i18n, vreq);
}
}

/**
* Notifies the administrator about a password reset for a user account.
*
* @param userAccount The user account for which the password is being reset.
* @param i18n The internationalization bundle for translating messages.
* @param vreq The Vitro request object.
*/
private void notifyAdmin(UserAccount userAccount, I18nBundle i18n, VitroRequest vreq) {
Map<String, Object> body = new HashMap<>();
body.put("userAccount", userAccount);
body.put("siteName", vreq.getAppBean().getApplicationName());
body.put("subject", i18n.text("password_reset_admin_notification_email_subject"));
body.put("textMessage", i18n.text("password_reset_admin_notification_email_plain_text"));
body.put("htmlMessage", i18n.text("password_reset_admin_notification_email_html"));

String adminEmailAddress =
Objects.requireNonNull(ConfigurationProperties.getInstance().getProperty("email.replyTo"));

sendEmailMessage(body, vreq, adminEmailAddress);
}

/**
* Sends an email message using Freemarker templates.
*
* @param body The map containing email body information.
* @param vreq The Vitro request object.
* @param recipientEmail The email address of the recipient.
*/
private void sendEmailMessage(Map<String, Object> body, VitroRequest vreq, String recipientEmail) {
FreemarkerEmailMessage emailMessage = FreemarkerEmailFactory
.createNewMessage(vreq);
emailMessage.addRecipient(Message.RecipientType.TO, userAccount.getEmailAddress());
emailMessage.addRecipient(Message.RecipientType.TO, recipientEmail);
emailMessage.setBodyMap(body);
emailMessage.processTemplate();
emailMessage.send();
}

/**
* Checks whether the admin should be notified about password resets.
*
* @return True if the admin should be notified, false otherwise.
*/
private boolean adminShouldBeNotified() {
String adminShouldBeNotified =
ConfigurationProperties.getInstance().getProperty("authentication.forgotPassword.notify-admin");

return Objects.nonNull(adminShouldBeNotified) && Boolean.parseBoolean(adminShouldBeNotified);
}

/**
* Generates a hash value for a given string.
*
Expand Down Expand Up @@ -298,11 +359,10 @@ private String getContactUrl(VitroRequest request) {
/**
* Checks whether the functionality for password recovery is enabled based on the configuration.
*
* @param vreq The VitroRequest object representing the current request context.
* @return 'true' if the functionality is enabled, 'false' otherwise.
*/
private boolean isFunctionalityEnabled(VitroRequest vreq) {
String enabled = ConfigurationProperties.getBean(vreq).getProperty("authentication.forgotPassword");
private boolean isFunctionalityEnabled() {
String enabled = ConfigurationProperties.getInstance().getProperty("authentication.forgotPassword");
return enabled != null && enabled.equalsIgnoreCase("enabled");
}

Expand Down
1 change: 1 addition & 0 deletions home/src/main/resources/config/example.runtime.properties
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,4 @@ proxy.eligibleTypeList = http://www.w3.org/2002/07/owl#Thing

# Feature toggle for forgot password functionality
authentication.forgotPassword = enabled
authentication.forgotPassword.notify-admin = true
Original file line number Diff line number Diff line change
Expand Up @@ -6357,3 +6357,27 @@ uil-data:no_individual_associated_with_id.Vitro
uil:hasApp "Vitro" ;
uil:hasKey "no_individual_associated_with_id" ;
uil:hasPackage "Vitro-languages" .

uil-data:password_reset_admin_notification_email_subject.Vitro
rdf:type owl:NamedIndividual ;
rdf:type uil:UILabel ;
rdfs:label "${siteName} reset password request"@en-CA ;
uil:hasApp "Vitro" ;
uil:hasKey "password_reset_admin_notification_email_subject" ;
uil:hasPackage "Vitro-languages" .

uil-data:password_reset_admin_notification_email_plain_text.Vitro
rdf:type owl:NamedIndividual ;
rdf:type uil:UILabel ;
rdfs:label "We have received a request to reset the password for user ${userAccount.firstName} ${userAccount.lastName} (${userAccount.emailAddress}).\n\nYou are receiving this because admin notifications for password resets are enabled.\n"@en-CA ;
uil:hasApp "Vitro" ;
uil:hasKey "password_reset_admin_notification_email_plain_text" ;
uil:hasPackage "Vitro-languages" .

uil-data:password_reset_admin_notification_email_html.Vitro
rdf:type owl:NamedIndividual ;
rdf:type uil:UILabel ;
rdfs:label "<p>\n We have received a request to reset the password for user ${userAccount.firstName} ${userAccount.lastName} (${userAccount.emailAddress}).\n </p> \n\n <p>\n You are receiving this because admin notifications for password resets are enabled.</p>"@en-CA ;
uil:hasApp "Vitro" ;
uil:hasKey "password_reset_admin_notification_email_html" ;
uil:hasPackage "Vitro-languages" .
Original file line number Diff line number Diff line change
Expand Up @@ -6357,3 +6357,27 @@ uil-data:no_individual_associated_with_id.Vitro
uil:hasApp "Vitro" ;
uil:hasKey "no_individual_associated_with_id" ;
uil:hasPackage "Vitro-languages" .

uil-data:password_reset_admin_notification_email_subject.Vitro
rdf:type owl:NamedIndividual ;
rdf:type uil:UILabel ;
rdfs:label "${siteName} reset password request"@en-CA ;
uil:hasApp "Vitro" ;
uil:hasKey "password_reset_admin_notification_email_subject" ;
uil:hasPackage "Vitro-languages" .

uil-data:password_reset_admin_notification_email_plain_text.Vitro
rdf:type owl:NamedIndividual ;
rdf:type uil:UILabel ;
rdfs:label "We have received a request to reset the password for user ${userAccount.firstName} ${userAccount.lastName} (${userAccount.emailAddress}).\n\nYou are receiving this because admin notifications for password resets are enabled.\n"@en-CA ;
uil:hasApp "Vitro" ;
uil:hasKey "password_reset_admin_notification_email_plain_text" ;
uil:hasPackage "Vitro-languages" .

uil-data:password_reset_admin_notification_email_html.Vitro
rdf:type owl:NamedIndividual ;
rdf:type uil:UILabel ;
rdfs:label "<p>\n We have received a request to reset the password for user ${userAccount.firstName} ${userAccount.lastName} (${userAccount.emailAddress}).\n </p> \n\n <p>\n You are receiving this because admin notifications for password resets are enabled.</p>"@en-CA ;
uil:hasApp "Vitro" ;
uil:hasKey "password_reset_admin_notification_email_html" ;
uil:hasPackage "Vitro-languages" .

0 comments on commit c2656a4

Please sign in to comment.