From 067a1cc1abccac9a0c5b5d6453a5061c35ba6797 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Fri, 14 Nov 2025 12:56:23 +0530 Subject: [PATCH 1/4] Fix url in password reset email --- .../user/UserPasswordResetManagerImpl.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 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 618ad5c86572..a0470a96ef54 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -48,6 +48,7 @@ import java.util.Set; import java.util.UUID; +import static org.apache.cloudstack.config.ApiServiceConfiguration.ManagementServerAddresses; import static org.apache.cloudstack.resourcedetail.UserDetailVO.PasswordResetToken; import static org.apache.cloudstack.resourcedetail.UserDetailVO.PasswordResetTokenExpiryDate; @@ -68,7 +69,7 @@ public class UserPasswordResetManagerImpl extends ManagerBase implements UserPas new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, String.class, "user.password.reset.mail.template", "Hello {{username}}!\n" + "You have requested to reset your password. Please click the following link to reset your password:\n" + - "{{{domainUrl}}}{{{resetLink}}}\n" + + "{{{resetLink}}}\n" + "If you did not request a password reset, please ignore this email.\n" + "\n" + "Regards,\n" + @@ -179,10 +180,14 @@ public void setResetTokenAndSend(UserAccount userAccount) { final String email = userAccount.getEmail(); final String username = userAccount.getUsername(); final String subject = "Password Reset Request"; - final String domainUrl = UserPasswordResetDomainURL.value(); + String domainUrl = UserPasswordResetDomainURL.value(); + if (StringUtils.isBlank(domainUrl)) { + domainUrl = ManagementServerAddresses.value().split(",")[0]; + } + domainUrl = domainUrl.replaceAll("/+$", ""); - String resetLink = String.format("/client/#/user/resetPassword?username=%s&token=%s", - username, resetToken); + String resetLink = String.format("%s/client/#/user/resetPassword?username=%s&token=%s", + domainUrl, username, resetToken); String content = getMessageBody(userAccount, resetToken, resetLink); SMTPMailProperties mailProperties = new SMTPMailProperties(); From 859f76508f534bb0fec3b6250024cd9c5f380c6e Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Fri, 5 Dec 2025 18:18:45 +0530 Subject: [PATCH 2/4] Include http:// in the default password reset url (same as earlier) --- .../org/apache/cloudstack/user/UserPasswordResetManager.java | 2 +- .../apache/cloudstack/user/UserPasswordResetManagerImpl.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java index 377e57b31e93..e5cdbb4042b2 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java @@ -78,7 +78,7 @@ public interface UserPasswordResetManager { ConfigKey UserPasswordResetDomainURL = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, String.class, "user.password.reset.mail.domain.url", null, - "Domain URL for reset password links sent to the user via email", true, + "Domain URL (along with http:// or https:// as applicable) for reset password links sent to the user via email", true, ConfigKey.Scope.Global); void setResetTokenAndSend(UserAccount userAccount); 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 a0470a96ef54..a2d130218c0c 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -182,10 +182,13 @@ public void setResetTokenAndSend(UserAccount userAccount) { final String subject = "Password Reset Request"; String domainUrl = UserPasswordResetDomainURL.value(); if (StringUtils.isBlank(domainUrl)) { - domainUrl = ManagementServerAddresses.value().split(",")[0]; + domainUrl = ManagementServerAddresses.value().split(",")[0]; } domainUrl = domainUrl.replaceAll("/+$", ""); + if (!domainUrl.startsWith("http://") && !domainUrl.startsWith("https://")) { + domainUrl = "http://" + domainUrl; + } String resetLink = String.format("%s/client/#/user/resetPassword?username=%s&token=%s", domainUrl, username, resetToken); String content = getMessageBody(userAccount, resetToken, resetLink); From 911bb6036a2fae165a19717339abd323cfbebc2e Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 8 Dec 2025 17:06:36 +0530 Subject: [PATCH 3/4] Updated http scheme for reset password url based on https.enabled in server.properties file --- .../org/apache/cloudstack/ServerDaemon.java | 6 ++-- .../user/UserPasswordResetManager.java | 4 ++- .../user/UserPasswordResetManagerImpl.java | 9 ++++-- .../cloud/utils/server/ServerProperties.java | 32 +++++++++++++++++-- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java index 196695e1fc6b..77a709c57559 100644 --- a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java +++ b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java @@ -76,9 +76,7 @@ public class ServerDaemon implements Daemon { private static final String SESSION_TIMEOUT = "session.timeout"; private static final String HTTP_ENABLE = "http.enable"; private static final String HTTP_PORT = "http.port"; - private static final String HTTPS_ENABLE = "https.enable"; private static final String HTTPS_PORT = "https.port"; - private static final String KEYSTORE_FILE = "https.keystore"; private static final String KEYSTORE_PASSWORD = "https.keystore.password"; private static final String WEBAPP_DIR = "webapp.dir"; private static final String ACCESS_LOG = "access.log"; @@ -142,9 +140,9 @@ public void init(final DaemonContext context) { setContextPath(properties.getProperty(CONTEXT_PATH, "/client")); setHttpEnable(Boolean.valueOf(properties.getProperty(HTTP_ENABLE, "true"))); setHttpPort(Integer.valueOf(properties.getProperty(HTTP_PORT, "8080"))); - setHttpsEnable(Boolean.valueOf(properties.getProperty(HTTPS_ENABLE, "false"))); + setHttpsEnable(Boolean.valueOf(properties.getProperty(ServerProperties.HTTPS_ENABLE, "false"))); setHttpsPort(Integer.valueOf(properties.getProperty(HTTPS_PORT, "8443"))); - setKeystoreFile(properties.getProperty(KEYSTORE_FILE)); + setKeystoreFile(properties.getProperty(ServerProperties.KEYSTORE_FILE)); setKeystorePassword(properties.getProperty(KEYSTORE_PASSWORD)); setWebAppLocation(properties.getProperty(WEBAPP_DIR)); setAccessLogFile(properties.getProperty(ACCESS_LOG, "access.log")); diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java index e5cdbb4042b2..c90e5cbb8230 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java @@ -78,7 +78,9 @@ public interface UserPasswordResetManager { ConfigKey UserPasswordResetDomainURL = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, String.class, "user.password.reset.mail.domain.url", null, - "Domain URL (along with http:// or https:// as applicable) for reset password links sent to the user via email", true, + "Domain URL (along with http:// or https:// as applicable) for reset password links sent to the user via email. " + + "If this is not set, CloudStack would determine the domain url based on the first management server from 'host' setting " + + "and http scheme based on the https.enabled flag from server.properties file in the management server.", true, ConfigKey.Scope.Global); void setResetTokenAndSend(UserAccount userAccount); 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 a2d130218c0c..2fa4a5243f5e 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -23,6 +23,7 @@ import com.cloud.user.dao.UserDao; import com.cloud.utils.StringUtils; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.server.ServerProperties; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; @@ -182,12 +183,16 @@ public void setResetTokenAndSend(UserAccount userAccount) { final String subject = "Password Reset Request"; String domainUrl = UserPasswordResetDomainURL.value(); if (StringUtils.isBlank(domainUrl)) { - domainUrl = ManagementServerAddresses.value().split(",")[0]; + domainUrl = ManagementServerAddresses.value().split(",")[0]; } domainUrl = domainUrl.replaceAll("/+$", ""); if (!domainUrl.startsWith("http://") && !domainUrl.startsWith("https://")) { - domainUrl = "http://" + domainUrl; + if (ServerProperties.isHttpsEnabled()) { + domainUrl = "https://" + domainUrl; + } else { + domainUrl = "http://" + domainUrl; + } } String resetLink = String.format("%s/client/#/user/resetPassword?username=%s&token=%s", domainUrl, username, resetToken); diff --git a/utils/src/main/java/com/cloud/utils/server/ServerProperties.java b/utils/src/main/java/com/cloud/utils/server/ServerProperties.java index 36d8614e68f1..0f2e838ba636 100644 --- a/utils/src/main/java/com/cloud/utils/server/ServerProperties.java +++ b/utils/src/main/java/com/cloud/utils/server/ServerProperties.java @@ -16,11 +16,15 @@ // under the License. package com.cloud.utils.server; +import com.cloud.utils.PropertiesUtil; import com.cloud.utils.crypt.EncryptionSecretKeyChecker; +import com.cloud.utils.StringUtils; import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; @@ -28,9 +32,12 @@ public class ServerProperties { protected Logger logger = LogManager.getLogger(getClass()); + public static final String HTTPS_ENABLE = "https.enable"; + public static final String KEYSTORE_FILE = "https.keystore"; + public static final String PASSWORD_ENCRYPTION_TYPE = "password.encryption.type"; + private static Properties properties = new Properties(); private static boolean loaded = false; - public static final String passwordEncryptionType = "password.encryption.type"; public synchronized static Properties getServerProperties(InputStream inputStream) { if (!loaded) { @@ -39,7 +46,7 @@ public synchronized static Properties getServerProperties(InputStream inputStrea serverProps.load(inputStream); EncryptionSecretKeyChecker checker = new EncryptionSecretKeyChecker(); - checker.check(serverProps, passwordEncryptionType); + checker.check(serverProps, PASSWORD_ENCRYPTION_TYPE); if (EncryptionSecretKeyChecker.useEncryption()) { EncryptionSecretKeyChecker.decryptAnyProperties(serverProps); @@ -56,4 +63,25 @@ public synchronized static Properties getServerProperties(InputStream inputStrea return properties; } + + public static boolean isHttpsEnabled() { + final File confFile = PropertiesUtil.findConfigFile("server.properties"); + if (confFile == null) { + return false; + } + + try { + InputStream is = new FileInputStream(confFile); + final Properties properties = ServerProperties.getServerProperties(is); + if (properties == null) { + return false; + } + + boolean httpsEnable = Boolean.parseBoolean(properties.getProperty(HTTPS_ENABLE, "false")); + String keystoreFile = properties.getProperty(KEYSTORE_FILE); + return httpsEnable && StringUtils.isNotEmpty(keystoreFile) && new File(keystoreFile).exists(); + } catch (final IOException e) { + return false; + } + } } From 538344dfdc2de467804b3044a1c7845936f7f463 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 27 Jan 2026 15:46:47 +0530 Subject: [PATCH 4/4] review comments --- .../org/apache/cloudstack/ServerDaemon.java | 9 ++-- .../user/UserPasswordResetManager.java | 2 +- .../user/UserPasswordResetManagerImpl.java | 15 ++++--- .../cloud/utils/server/ServerProperties.java | 42 ++++++++++--------- 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java index 77a709c57559..30020cf89298 100644 --- a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java +++ b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java @@ -74,9 +74,6 @@ public class ServerDaemon implements Daemon { private static final String BIND_INTERFACE = "bind.interface"; private static final String CONTEXT_PATH = "context.path"; private static final String SESSION_TIMEOUT = "session.timeout"; - private static final String HTTP_ENABLE = "http.enable"; - private static final String HTTP_PORT = "http.port"; - private static final String HTTPS_PORT = "https.port"; private static final String KEYSTORE_PASSWORD = "https.keystore.password"; private static final String WEBAPP_DIR = "webapp.dir"; private static final String ACCESS_LOG = "access.log"; @@ -138,10 +135,10 @@ public void init(final DaemonContext context) { } setBindInterface(properties.getProperty(BIND_INTERFACE, null)); setContextPath(properties.getProperty(CONTEXT_PATH, "/client")); - setHttpEnable(Boolean.valueOf(properties.getProperty(HTTP_ENABLE, "true"))); - setHttpPort(Integer.valueOf(properties.getProperty(HTTP_PORT, "8080"))); + setHttpEnable(Boolean.valueOf(properties.getProperty(ServerProperties.HTTP_ENABLE, "true"))); + setHttpPort(Integer.valueOf(properties.getProperty(ServerProperties.HTTP_PORT, "8080"))); setHttpsEnable(Boolean.valueOf(properties.getProperty(ServerProperties.HTTPS_ENABLE, "false"))); - setHttpsPort(Integer.valueOf(properties.getProperty(HTTPS_PORT, "8443"))); + setHttpsPort(Integer.valueOf(properties.getProperty(ServerProperties.HTTPS_PORT, "8443"))); setKeystoreFile(properties.getProperty(ServerProperties.KEYSTORE_FILE)); setKeystorePassword(properties.getProperty(KEYSTORE_PASSWORD)); setWebAppLocation(properties.getProperty(WEBAPP_DIR)); diff --git a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java index c90e5cbb8230..ca14f6a1654e 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManager.java @@ -78,7 +78,7 @@ public interface UserPasswordResetManager { ConfigKey UserPasswordResetDomainURL = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, String.class, "user.password.reset.mail.domain.url", null, - "Domain URL (along with http:// or https:// as applicable) for reset password links sent to the user via email. " + + "Domain URL (along with scheme - http:// or https:// and port as applicable) for reset password links sent to the user via email. " + "If this is not set, CloudStack would determine the domain url based on the first management server from 'host' setting " + "and http scheme based on the https.enabled flag from server.properties file in the management server.", true, ConfigKey.Scope.Global); 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 2fa4a5243f5e..c62bca8eca4b 100644 --- a/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/user/UserPasswordResetManagerImpl.java @@ -183,17 +183,22 @@ public void setResetTokenAndSend(UserAccount userAccount) { final String subject = "Password Reset Request"; String domainUrl = UserPasswordResetDomainURL.value(); if (StringUtils.isBlank(domainUrl)) { - domainUrl = ManagementServerAddresses.value().split(",")[0]; - } - domainUrl = domainUrl.replaceAll("/+$", ""); - - if (!domainUrl.startsWith("http://") && !domainUrl.startsWith("https://")) { + 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 resetLink = String.format("%s/client/#/user/resetPassword?username=%s&token=%s", domainUrl, username, resetToken); String content = getMessageBody(userAccount, resetToken, resetLink); diff --git a/utils/src/main/java/com/cloud/utils/server/ServerProperties.java b/utils/src/main/java/com/cloud/utils/server/ServerProperties.java index 0f2e838ba636..9e81fff90f01 100644 --- a/utils/src/main/java/com/cloud/utils/server/ServerProperties.java +++ b/utils/src/main/java/com/cloud/utils/server/ServerProperties.java @@ -16,7 +16,6 @@ // under the License. package com.cloud.utils.server; -import com.cloud.utils.PropertiesUtil; import com.cloud.utils.crypt.EncryptionSecretKeyChecker; import com.cloud.utils.StringUtils; import org.apache.commons.io.IOUtils; @@ -24,7 +23,6 @@ import org.apache.logging.log4j.LogManager; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Properties; @@ -32,13 +30,21 @@ public class ServerProperties { protected Logger logger = LogManager.getLogger(getClass()); + public static final String HTTP_ENABLE = "http.enable"; + public static final String HTTP_PORT = "http.port"; public static final String HTTPS_ENABLE = "https.enable"; + public static final String HTTPS_PORT = "https.port"; public static final String KEYSTORE_FILE = "https.keystore"; public static final String PASSWORD_ENCRYPTION_TYPE = "password.encryption.type"; private static Properties properties = new Properties(); private static boolean loaded = false; + private static int httpPort = 8080; + + private static boolean httpsEnable = false; + private static int httpsPort = 8443; + public synchronized static Properties getServerProperties(InputStream inputStream) { if (!loaded) { Properties serverProps = new Properties(); @@ -57,6 +63,13 @@ public synchronized static Properties getServerProperties(InputStream inputStrea IOUtils.closeQuietly(inputStream); } + httpPort = Integer.parseInt(serverProps.getProperty(ServerProperties.HTTP_PORT, "8080")); + + boolean httpsEnabled = Boolean.parseBoolean(serverProps.getProperty(ServerProperties.HTTPS_ENABLE, "false")); + String keystoreFile = serverProps.getProperty(KEYSTORE_FILE); + httpsEnable = httpsEnabled && StringUtils.isNotEmpty(keystoreFile) && new File(keystoreFile).exists(); + httpsPort = Integer.parseInt(serverProps.getProperty(ServerProperties.HTTPS_PORT, "8443")); + properties = serverProps; loaded = true; } @@ -64,24 +77,15 @@ public synchronized static Properties getServerProperties(InputStream inputStrea return properties; } - public static boolean isHttpsEnabled() { - final File confFile = PropertiesUtil.findConfigFile("server.properties"); - if (confFile == null) { - return false; - } + public static int getHttpPort() { + return httpPort; + } - try { - InputStream is = new FileInputStream(confFile); - final Properties properties = ServerProperties.getServerProperties(is); - if (properties == null) { - return false; - } + public static boolean isHttpsEnabled() { + return httpsEnable; + } - boolean httpsEnable = Boolean.parseBoolean(properties.getProperty(HTTPS_ENABLE, "false")); - String keystoreFile = properties.getProperty(KEYSTORE_FILE); - return httpsEnable && StringUtils.isNotEmpty(keystoreFile) && new File(keystoreFile).exists(); - } catch (final IOException e) { - return false; - } + public static int getHttpsPort() { + return httpsPort; } }