Skip to content

Commit

Permalink
Merged password recovery feature
Browse files Browse the repository at this point in the history
  • Loading branch information
GuilhemSempere committed Aug 21, 2024
1 parent 74ab442 commit 4877e8e
Show file tree
Hide file tree
Showing 15 changed files with 210 additions and 141 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target/
/.externalToolBuilders/
/.settings/
4 changes: 2 additions & 2 deletions bundle_files/osx/updateGigwa.command
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if [ -d "$1" ]; then

#copy configuration files from saved folder to new gigwa folder
cp -av $3/$backup_dir.$DATE/WEB-INF/classes/applicationContext-data.xml $2/WEB-INF/classes/applicationContext-data.xml
# cp -av $3/$backup_dir.$DATE/WEB-INF/classes/applicationContext-security.xml $2/WEB-INF/classes/applicationContext-security.xml
cp -av $3/$backup_dir.$DATE/WEB-INF/classes/applicationContext-MVC.xml $2/WEB-INF/classes/applicationContext-MVC.xml
cp -av $3/$backup_dir.$DATE/WEB-INF/classes/datasources.properties $2/WEB-INF/classes/datasources.properties
cp -av $3/$backup_dir.$DATE/WEB-INF/classes/users.properties $2/WEB-INF/classes/users.properties
cp -av $3/$backup_dir.$DATE/WEB-INF/classes/config.properties $2/WEB-INF/classes/config.properties
Expand Down Expand Up @@ -62,4 +62,4 @@ fi
chmod -R 755 $2

echo -------------------------------
echo "Update complete. You may want to apply chown -R on the updated folder."
echo "Update complete. You may want to apply chown -R on the updated folder."
4 changes: 2 additions & 2 deletions bundle_files/ubuntu/updateGigwa.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ if [ -d "$1" ]; then

#copy configuration files from saved folder to new gigwa folder
cp -avr $3/$backup_dir.$DATE/WEB-INF/classes/applicationContext-data.xml $2/WEB-INF/classes/applicationContext-data.xml
# cp -avr $3/$backup_dir.$DATE/WEB-INF/classes/applicationContext-security.xml $2/WEB-INF/classes/applicationContext-security.xml
cp -avr $3/$backup_dir.$DATE/WEB-INF/classes/applicationContext-MVC.xml $2/WEB-INF/classes/applicationContext-MVC.xml
cp -avr $3/$backup_dir.$DATE/WEB-INF/classes/datasources.properties $2/WEB-INF/classes/datasources.properties
cp -avr $3/$backup_dir.$DATE/WEB-INF/classes/users.properties $2/WEB-INF/classes/users.properties
cp -avr $3/$backup_dir.$DATE/WEB-INF/classes/config.properties $2/WEB-INF/classes/config.properties
Expand All @@ -61,4 +61,4 @@ fi
chmod -R 755 $2

echo -------------------------------
echo "Update complete. You may want to apply chown -R on the updated folder."
echo "Update complete. You may want to apply chown -R on the updated folder."
4 changes: 2 additions & 2 deletions bundle_files/windows/updateGigwa.bat
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ xcopy /seyi %1 %2

::copy configuration files from saved folder to new gigwa folder
xcopy /seyi "%3\%backup_dir%.%d%\WEB-INF\classes\applicationContext-data.xml" "%2\WEB-INF\classes\applicationContext-data.xml"
::xcopy /seyi "%3\%backup_dir%.%d%\WEB-INF\classes\applicationContext-security.xml" "%2\WEB-INF\classes\applicationContext-security.xml"
xcopy /seyi "%3\%backup_dir%.%d%\WEB-INF\classes\applicationContext-MVC.xml" "%2\WEB-INF\classes\applicationContext-MVC.xml"
xcopy /seyi "%3\%backup_dir%.%d%\WEB-INF\classes\datasources.properties" "%2\WEB-INF\classes\datasources.properties"
xcopy /seyi "%3\%backup_dir%.%d%\WEB-INF\classes\users.properties" "%2\WEB-INF\classes\users.properties"
xcopy /seyi "%3\%backup_dir%.%d%\WEB-INF\classes\config.properties" "%2\WEB-INF\classes\config.properties"
Expand All @@ -74,4 +74,4 @@ powershell -Command "$previousPE = $(Select-String -Path %3\%backup_dir%.%d%\WEB
echo -------------------------------
echo Update complete.

:eof
:eof
30 changes: 0 additions & 30 deletions src/main/java/fr/cirad/security/ResetCodeExpirationFilter.java

This file was deleted.

163 changes: 117 additions & 46 deletions src/main/java/fr/cirad/service/PasswordResetService.java
Original file line number Diff line number Diff line change
@@ -1,105 +1,176 @@
package fr.cirad.service;

import fr.cirad.security.ReloadableInMemoryDaoImpl;
import fr.cirad.security.UserWithMethod;
import fr.cirad.tools.AppConfig;
import fr.cirad.web.controller.BackOfficeController;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.LocalDateTime;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.time.LocalDateTime;
import fr.cirad.security.ReloadableInMemoryDaoImpl;
import fr.cirad.security.UserWithMethod;
import fr.cirad.tools.AppConfig;
import fr.cirad.web.controller.BackOfficeController;
import fr.cirad.web.controller.GigwaAuthenticationController;

@Service
public class PasswordResetService {

private static final Logger LOG = Logger.getLogger(PasswordResetService.class);

private static final String RESET_CODE_KEY = "resetCode";
private static final String RESET_EMAIL_KEY = "resetEmail";
private static final String RESET_EXPIRATION_KEY = "resetExpiration";
public static final String RESET_CODE_KEY = "resetCode";
public static final String RESET_EMAIL_KEY = "resetEmail";
public static final String RESET_EXPIRATION_KEY = "resetExpiration";

@Autowired
private JavaMailSender mailSender;
@Autowired(required=false)
private JavaMailSenderImpl mailSender;

@Autowired
private AppConfig appConfig;

@Autowired
private ReloadableInMemoryDaoImpl userDao;

public boolean seemsProperlyConfigured() {
try (Socket socket = new Socket(mailSender.getHost(), mailSender.getPort())) {
return true;
} catch (Exception e) {
if (mailSender != null) {
LOG.error("JavaMailSenderImpl not properly configured", e);
mailSender = null; // only log this error once
}
return false;
}
}

public String generateResetCode() {
return String.format("%08d", new java.util.Random().nextInt(100000000));
}

public boolean sendResetPasswordEmail(String email, HttpSession session, HttpServletRequest request) {
public boolean sendResetPasswordEmail(String email, HttpSession session, HttpServletRequest request) throws MessagingException, SocketException, UnknownHostException {
UserWithMethod user = userDao.getUserWithMethodByEmailAddress(email);
if (user == null) {
return true; // We return true to not disclose if the email exists
}

String resetCode = generateResetCode();
String sWebAppRoot = request.getHeader("referer");
if (sWebAppRoot != null)
sWebAppRoot = sWebAppRoot.replaceFirst(GigwaAuthenticationController.LOGIN_LOST_PASSWORD_URL, "");
else {
sWebAppRoot = appConfig.get("enforcedWebapRootUrl");
if (sWebAppRoot == null) {
String computedBaseURL = BackOfficeController.determinePublicHostName(request);
if (computedBaseURL != null)
sWebAppRoot = computedBaseURL + request.getContextPath();
}
}
if (sWebAppRoot == null)
LOG.warn("Unable to determine password reset link. None will be mentioned in the e-mail!");

String subject = "Gigwa - Password reset request";
String emailContent = "Hello,\n\nYou have requested to reset your password"
+ (sWebAppRoot == null ? "" : " for the following Gigwa instance: " + sWebAppRoot + GigwaAuthenticationController.LOGIN_RESET_PASSWORD_URL)
+ ".\nYour password reset code is: " + resetCode + "\nPlease enter this code in the application to reset your password. This code will expire in 5 minutes.\n";

MimeMessage message = mailSender.createMimeMessage();
MimeMultipart multipart = new MimeMultipart("alternative");
MimeBodyPart messageTxtBody = new MimeBodyPart();

messageTxtBody.setContent(emailContent, "text/plain; charset=" + mailSender.getDefaultEncoding());

multipart.addBodyPart(messageTxtBody);

message.setContent(multipart);
message.setSentDate(new java.util.Date());
String adminEmail = appConfig.get("adminEmail");
message.addFrom(new InternetAddress[] {new InternetAddress(adminEmail != null ? adminEmail : getNoReplyAddress(mailSender))});

message.setSubject(subject);
message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
message.saveChanges();

mailSender.send(message);

session.setAttribute(RESET_CODE_KEY, resetCode);
session.setAttribute(RESET_EMAIL_KEY, email);
session.setAttribute(RESET_EXPIRATION_KEY, LocalDateTime.now().plusMinutes(5));
return true;
}

public static String getNoReplyAddress(JavaMailSenderImpl mailSender) throws IllegalArgumentException{
String host = mailSender.getHost();

// Try regex replacement for common prefixes
String cleanedHost = host.replaceFirst("^(smtp|mail)\\.", "");

// If regex didn't change the host, fall back to string manipulation
if (cleanedHost.equals(host)) {
int secondLastDotIndex = host.lastIndexOf('.', host.lastIndexOf('.') - 1);
if (secondLastDotIndex != -1)
cleanedHost = host.substring(secondLastDotIndex + 1);
}

try {
SimpleMailMessage message = new SimpleMailMessage();

String sWebAppRoot = appConfig.get("enforcedWebapRootUrl");
String enforcedWebapRootUrl = (sWebAppRoot == null ? BackOfficeController.determinePublicHostName(request) + request.getContextPath() : sWebAppRoot);
if (enforcedWebapRootUrl == null || enforcedWebapRootUrl.trim().isEmpty())
LOG.warn("enforcedWebapRootUrl is not set in the application.properties file. Using the default value.");
// Construct the "noreply" email address
String noReplyAddress = "noreply@" + cleanedHost;

String subject = "Gigwa - Password reset request";
String emailContent = "Hello,\n\nYou have requested to reset your password for the following Gigwa instance: " + enforcedWebapRootUrl + "\nYour password reset code is: " + resetCode + "\nPlease enter this code in the application to reset your password. This code will expire in 5 minutes.\n";
// Validate the email address
if (!isValidEmailAddress(noReplyAddress))
throw new IllegalArgumentException("Generated noreply address is not valid: " + noReplyAddress);

message.setTo(email);
message.setSubject(subject);
message.setText(emailContent);
return noReplyAddress;
}

mailSender.send(message);
return true;
} catch (Exception e) {
LOG.error("Unable to send password reset email", e);
return false;
}
private static boolean isValidEmailAddress(String email) {
// Simple email validation regex
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
return email.matches(emailRegex);
}

public boolean validateResetCode(String code, HttpSession session) {
String storedCode = (String) session.getAttribute(RESET_CODE_KEY);
LocalDateTime expiration = (LocalDateTime) session.getAttribute(RESET_EXPIRATION_KEY);

if (storedCode == null || !storedCode.equals(code) || expiration == null) {
String storedCode = (String) session.getAttribute(RESET_CODE_KEY);
if (expiration == null || storedCode == null || expiration.isBefore(LocalDateTime.now())) { // Clear expired or invalid reset information
session.removeAttribute(RESET_CODE_KEY);
session.removeAttribute(RESET_EMAIL_KEY);
session.removeAttribute(RESET_EXPIRATION_KEY);
return false;
}

return expiration.isAfter(LocalDateTime.now());
if (!storedCode.equals(code))
return false; // failed attempt

return true;
}

public boolean updatePassword(String code, String newPassword, HttpSession session) {
if (!validateResetCode(code, session)) {
if (!validateResetCode(code, session))
return false;
}

String email = (String) session.getAttribute(RESET_EMAIL_KEY);
UserWithMethod user = userDao.getUserWithMethodByEmailAddress(email);
if (user == null) {
if (user == null)
return false;
}

try {
userDao.saveOrUpdateUser(user.getUsername(), newPassword,
user.getAuthorities(),
user.isEnabled(), user.getMethod(), user.getEmail());
} catch (IOException e) {
e.printStackTrace();
userDao.saveOrUpdateUser(user.getUsername(), newPassword, user.getAuthorities(), user.isEnabled(), user.getMethod(), user.getEmail());
}
catch (IOException e) {
LOG.error("Error while overriding user password", e);
return false;
}

Expand Down
Loading

0 comments on commit 4877e8e

Please sign in to comment.