From e529c2f5b04ad9cb7e971d59f6b9e3720f13d6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20B=C3=B6ck?= Date: Fri, 27 Mar 2026 08:43:07 -0300 Subject: [PATCH 1/2] Password reset link domain obtention flow refactoring --- .../cloudstack/context/CallContext.java | 9 +++ .../main/java/com/cloud/api/ApiServlet.java | 1 + .../user/UserPasswordResetManagerImpl.java | 70 ++++++++++++++----- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/context/CallContext.java b/api/src/main/java/org/apache/cloudstack/context/CallContext.java index 5e0c60184f4f..f9832ea2f225 100644 --- a/api/src/main/java/org/apache/cloudstack/context/CallContext.java +++ b/api/src/main/java/org/apache/cloudstack/context/CallContext.java @@ -66,6 +66,7 @@ protected Stack initialValue() { private final Map apiResourcesUuids = new HashMap<>(); private Project project; private String apiName; + private String requestRemoteAddress; static EntityManager s_entityMgr; @@ -401,6 +402,14 @@ public Map getContextParameters() { return context; } + public String getRequestRemoteAddress() { + return requestRemoteAddress; + } + + public void setRequestRemoteAddress(String remoteAddress) { + this.requestRemoteAddress = remoteAddress; + } + public void putContextParameters(Map details){ if (details == null) return; for(Map.Entryentry : details.entrySet()){ diff --git a/server/src/main/java/com/cloud/api/ApiServlet.java b/server/src/main/java/com/cloud/api/ApiServlet.java index bfeb3d276e4e..ba6094ee12a7 100644 --- a/server/src/main/java/com/cloud/api/ApiServlet.java +++ b/server/src/main/java/com/cloud/api/ApiServlet.java @@ -216,6 +216,7 @@ void processRequestInContext(final HttpServletRequest req, final HttpServletResp HttpUtils.RESPONSE_TYPE_XML, ApiServer.JSONcontentType.value()); return; } + CallContext.current().setRequestRemoteAddress(req.getServerName()); final StringBuilder auditTrailSb = new StringBuilder(128); auditTrailSb.append(" ").append(remoteAddress.getHostAddress()); diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java index d6b5dbb18f9f..7c5a78afa33d 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -29,8 +29,10 @@ import com.github.mustachejava.MustacheFactory; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.gui.theme.dao.GuiThemeDetailsDao; import org.apache.cloudstack.resourcedetail.UserDetailVO; import org.apache.cloudstack.resourcedetail.dao.UserDetailsDao; import org.apache.cloudstack.utils.mailing.MailAddress; @@ -45,6 +47,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -65,6 +68,9 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas @Inject private UserDao userDao; + @Inject + private GuiThemeDetailsDao guiThemeDetailsDao; + private SMTPMailSender mailSender; public static ConfigKey PasswordResetMailTemplate = @@ -182,26 +188,11 @@ public void setResetTokenAndSend(UserAccount userAccount) { final String email = userAccount.getEmail(); final String username = userAccount.getUsername(); final String subject = "Password Reset Request"; - String domainUrl = UserPasswordResetDomainURL.value(); - if (StringUtils.isBlank(domainUrl)) { - String mgmtServerAddr = ManagementServerAddresses.value().split(",")[0]; - if (ServerProperties.isHttpsEnabled()) { - domainUrl = "https://" + mgmtServerAddr + ":" + ServerProperties.getHttpsPort(); - } else { - domainUrl = "http://" + mgmtServerAddr + ":" + ServerProperties.getHttpPort(); - } - } else if (!domainUrl.startsWith("http://") && !domainUrl.startsWith("https://")) { - if (ServerProperties.isHttpsEnabled()) { - domainUrl = "https://" + domainUrl; - } else { - domainUrl = "http://" + domainUrl; - } - } - - domainUrl = domainUrl.replaceAll("/+$", ""); + String requestDomain = CallContext.current().getRequestRemoteAddress(); + String resetLinkDomain = getResetLinkDomain(requestDomain); String resetLink = String.format("%s/client/#/user/resetPassword?username=%s&token=%s", - domainUrl, username, resetToken); + resetLinkDomain, username, resetToken); String content = getMessageBody(userAccount, resetToken, resetLink); SMTPMailProperties mailProperties = new SMTPMailProperties(); @@ -222,6 +213,49 @@ public void setResetTokenAndSend(UserAccount userAccount) { userAccount, userAccount.getAccountId(), userAccount.getDomainId(), email, resetTokenExpiryTime); } + private String getResetLinkDomain(String requestDomain) { + String domain = ""; + + if (StringUtils.isNotBlank(requestDomain)) { + logger.debug("Searching for GUI theme with common name that matches the request's domain: [{}]", requestDomain); + List commonNameDetails = guiThemeDetailsDao.listGuiThemeIdsByCommonName(requestDomain); + + if (!commonNameDetails.isEmpty()) { + logger.debug("GUI theme with ID {} was found; using request's domain for password reset link.", commonNameDetails.get(0)); + domain = requestDomain; + } else { + logger.debug("No GUI theme was found with a common name that matches the request's domain."); + } + } + + String configurationDomain = UserPasswordResetDomainURL.value(); + if (StringUtils.isBlank(domain) && !StringUtils.isBlank(configurationDomain)) { + logger.debug("Defaulting reset link's domain to the [{}] configuration value: [{}].", UserPasswordResetDomainURL.key(), UserPasswordResetDomainURL.value()); + domain = configurationDomain; + } + + if (StringUtils.isBlank(domain)) { + logger.debug("Using the first IP address in the [{}] configuration for the reset password email domain because the [{}] configuration is not defined.", ManagementServerAddresses.key(), UserPasswordResetDomainURL.key()); + domain = ManagementServerAddresses.value().split(",")[0]; + + if (ServerProperties.isHttpsEnabled()) { + domain = "https://" + domain + ":" + ServerProperties.getHttpsPort(); + } else { + domain = "http://" + domain + ":" + ServerProperties.getHttpPort(); + } + } + + if (!domain.startsWith("http")) { + if (ServerProperties.isHttpsEnabled()) { + domain = "https://" + domain; + } else { + domain = "http://" + domain; + } + } + + return domain.replaceAll("/+$", ""); + } + @Override public boolean validateAndResetPassword(UserAccount user, String token, String password) { UserDetailVO resetTokenDetail = userDetailsDao.findDetail(user.getId(), PasswordResetToken); From dc4bcb1c7c292b40bcf5f16c450e302c09b1daa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20B=C3=B6ck?= Date: Thu, 21 May 2026 09:09:51 -0300 Subject: [PATCH 2/2] Refactor reset domain link formatting --- .../user/UserPasswordResetManagerImpl.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java index 7c5a78afa33d..da1bca516ed4 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -38,6 +38,7 @@ import org.apache.cloudstack.utils.mailing.MailAddress; import org.apache.cloudstack.utils.mailing.SMTPMailProperties; import org.apache.cloudstack.utils.mailing.SMTPMailSender; +import org.apache.http.conn.util.InetAddressUtils; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -191,8 +192,9 @@ public void setResetTokenAndSend(UserAccount userAccount) { String requestDomain = CallContext.current().getRequestRemoteAddress(); String resetLinkDomain = getResetLinkDomain(requestDomain); + String formattedResetLinkDomain = formatResetLinkDomain(resetLinkDomain); String resetLink = String.format("%s/client/#/user/resetPassword?username=%s&token=%s", - resetLinkDomain, username, resetToken); + formattedResetLinkDomain, username, resetToken); String content = getMessageBody(userAccount, resetToken, resetLink); SMTPMailProperties mailProperties = new SMTPMailProperties(); @@ -214,46 +216,41 @@ public void setResetTokenAndSend(UserAccount userAccount) { } private String getResetLinkDomain(String requestDomain) { - String domain = ""; - if (StringUtils.isNotBlank(requestDomain)) { logger.debug("Searching for GUI theme with common name that matches the request's domain: [{}]", requestDomain); List commonNameDetails = guiThemeDetailsDao.listGuiThemeIdsByCommonName(requestDomain); if (!commonNameDetails.isEmpty()) { logger.debug("GUI theme with ID {} was found; using request's domain for password reset link.", commonNameDetails.get(0)); - domain = requestDomain; + return requestDomain; } else { logger.debug("No GUI theme was found with a common name that matches the request's domain."); } } String configurationDomain = UserPasswordResetDomainURL.value(); - if (StringUtils.isBlank(domain) && !StringUtils.isBlank(configurationDomain)) { + if (StringUtils.isNotBlank(configurationDomain)) { logger.debug("Defaulting reset link's domain to the [{}] configuration value: [{}].", UserPasswordResetDomainURL.key(), UserPasswordResetDomainURL.value()); - domain = configurationDomain; + return configurationDomain; } - if (StringUtils.isBlank(domain)) { - logger.debug("Using the first IP address in the [{}] configuration for the reset password email domain because the [{}] configuration is not defined.", ManagementServerAddresses.key(), UserPasswordResetDomainURL.key()); - domain = ManagementServerAddresses.value().split(",")[0]; + logger.debug("Using the first IP address in the [{}] configuration for the reset password email domain because the [{}] configuration is not defined.", ManagementServerAddresses.key(), UserPasswordResetDomainURL.key()); + return ManagementServerAddresses.value().split(",")[0]; + } - if (ServerProperties.isHttpsEnabled()) { - domain = "https://" + domain + ":" + ServerProperties.getHttpsPort(); - } else { - domain = "http://" + domain + ":" + ServerProperties.getHttpPort(); - } + private String formatResetLinkDomain(String resetLinkDomain) { + String protocol = ServerProperties.isHttpsEnabled() ? "https" : "http"; + + if (InetAddressUtils.isIPv4Address(resetLinkDomain)) { + int port = protocol.equals("https") ? ServerProperties.getHttpsPort() : ServerProperties.getHttpPort(); + resetLinkDomain = resetLinkDomain + ":" + port; } - if (!domain.startsWith("http")) { - if (ServerProperties.isHttpsEnabled()) { - domain = "https://" + domain; - } else { - domain = "http://" + domain; - } + if (!resetLinkDomain.startsWith("http")) { + resetLinkDomain = protocol + "://" + resetLinkDomain; } - return domain.replaceAll("/+$", ""); + return resetLinkDomain.replaceAll("/+$", ""); } @Override