diff --git a/common/src/java/com/zimbra/common/soap/AdminConstants.java b/common/src/java/com/zimbra/common/soap/AdminConstants.java
index 09469853c6f..5b72e6cd7a5 100644
--- a/common/src/java/com/zimbra/common/soap/AdminConstants.java
+++ b/common/src/java/com/zimbra/common/soap/AdminConstants.java
@@ -42,6 +42,8 @@ public final class AdminConstants {
public static final String E_AUTH_REQUEST = "AuthRequest";
public static final String E_AUTH_RESPONSE = "AuthResponse";
+ public static final String E_CHANGE_PASSWORD_REQUEST = "ChangePasswordRequest";
+ public static final String E_CHANGE_PASSWORD_RESPONSE = "ChangePasswordResponse";
public static final String E_CREATE_ACCOUNT_REQUEST = "CreateAccountRequest";
public static final String E_CREATE_ACCOUNT_RESPONSE = "CreateAccountResponse";
public static final String E_CREATE_GAL_SYNC_ACCOUNT_REQUEST = "CreateGalSyncAccountRequest";
@@ -599,6 +601,8 @@ public final class AdminConstants {
public static final QName SEARCH_ACCOUNTS_RESPONSE = QName.get(E_SEARCH_ACCOUNTS_RESPONSE, NAMESPACE);
public static final QName RENAME_ACCOUNT_REQUEST = QName.get(E_RENAME_ACCOUNT_REQUEST, NAMESPACE);
public static final QName RENAME_ACCOUNT_RESPONSE = QName.get(E_RENAME_ACCOUNT_RESPONSE, NAMESPACE);
+ public static final QName CHANGE_PASSWORD_REQUEST = QName.get(E_CHANGE_PASSWORD_REQUEST, NAMESPACE);
+ public static final QName CHANGE_PASSWORD_RESPONSE = QName.get(E_CHANGE_PASSWORD_RESPONSE, NAMESPACE);
public static final QName CHANGE_PRIMARY_EMAIL_REQUEST = QName.get(E_CHANGE_PRIMARY_EMAIL_REQUEST, NAMESPACE);
public static final QName CHANGE_PRIMARY_EMAIL_RESPONSE = QName.get(E_CHANGE_PRIMARY_EMAIL_RESPONSE, NAMESPACE);
diff --git a/soap/src/java/com/zimbra/soap/JaxbUtil.java b/soap/src/java/com/zimbra/soap/JaxbUtil.java
index f2217eb5d08..2ba30172be2 100644
--- a/soap/src/java/com/zimbra/soap/JaxbUtil.java
+++ b/soap/src/java/com/zimbra/soap/JaxbUtil.java
@@ -252,6 +252,8 @@ public final class JaxbUtil {
com.zimbra.soap.admin.message.CancelPendingAccountOnlyRemoteWipeResponse.class,
com.zimbra.soap.admin.message.CancelPendingRemoteWipeRequest.class,
com.zimbra.soap.admin.message.CancelPendingRemoteWipeResponse.class,
+ com.zimbra.soap.admin.message.ChangePasswordRequest.class,
+ com.zimbra.soap.admin.message.ChangePasswordResponse.class,
com.zimbra.soap.admin.message.CheckAuthConfigRequest.class,
com.zimbra.soap.admin.message.CheckAuthConfigResponse.class,
com.zimbra.soap.admin.message.CheckBlobConsistencyRequest.class,
diff --git a/soap/src/java/com/zimbra/soap/admin/message/ChangePasswordRequest.java b/soap/src/java/com/zimbra/soap/admin/message/ChangePasswordRequest.java
new file mode 100644
index 00000000000..d566afe03eb
--- /dev/null
+++ b/soap/src/java/com/zimbra/soap/admin/message/ChangePasswordRequest.java
@@ -0,0 +1,113 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Zimbra Collaboration Suite Server
+ * Copyright (C) 2024 Synacor, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software Foundation,
+ * version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ * ***** END LICENSE BLOCK *****
+ */
+package com.zimbra.soap.admin.message;
+
+import com.zimbra.common.soap.AccountConstants;
+import com.zimbra.common.soap.AdminConstants;
+import com.zimbra.soap.account.type.AuthToken;
+import com.zimbra.soap.type.AccountSelector;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name= AdminConstants.E_CHANGE_PASSWORD_REQUEST)
+@XmlType(propOrder = {})
+public class ChangePasswordRequest {
+ /**
+ * @zm-api-field-description Details of the account
+ */
+ @XmlElement(name=AccountConstants.E_ACCOUNT, required=true)
+ private AccountSelector account;
+ /**
+ * @zm-api-field-description Old password
+ */
+ @XmlElement(name=AccountConstants.E_OLD_PASSWORD, required=true)
+ private String oldPassword;
+ /**
+ * @zm-api-field-description New Password to assign
+ */
+ @XmlElement(name=AccountConstants.E_PASSWORD, required=true)
+ private String password;
+ /**
+ * @zm-api-field-tag virtual-host
+ * @zm-api-field-description if specified virtual-host is used to determine the domain of the account name,
+ * if it does not include a domain component. For example, if the domain foo.com has a zimbraVirtualHostname of
+ * "mail.foo.com", and an auth request comes in for "joe" with a virtualHost of "mail.foo.com", then the request
+ * will be equivalent to logging in with "joe@foo.com".
+ */
+ @XmlElement(name=AccountConstants.E_VIRTUAL_HOST, required=false)
+ private String virtualHost;
+
+ @XmlElement(name=AccountConstants.E_DRYRUN, required=false)
+ private boolean dryRun;
+
+ @XmlElement(name=AccountConstants.E_AUTH_TOKEN /* authToken */, required=false)
+ private AuthToken authToken;
+
+ public ChangePasswordRequest() {
+ }
+
+ public ChangePasswordRequest(AccountSelector account, String oldPassword, String newPassword) {
+ setAccount(account);
+ setOldPassword(oldPassword);
+ setPassword(newPassword);
+ }
+
+ public ChangePasswordRequest(AccountSelector account, String oldPassword, String newPassword, boolean dryRun) {
+ setAccount(account);
+ setOldPassword(oldPassword);
+ setPassword(newPassword);
+ setDryRun(dryRun);
+ }
+
+ public AccountSelector getAccount() { return account; }
+ public String oldPassword() { return oldPassword; }
+ public String getPassword() { return password; }
+ public String getVirtualHost() { return virtualHost; }
+
+ public ChangePasswordRequest setAccount(AccountSelector account) {
+ this.account = account;
+ return this;
+ }
+
+ public ChangePasswordRequest setOldPassword(String oldPassword) {
+ this.oldPassword = oldPassword;
+ return this;
+ }
+
+ public ChangePasswordRequest setPassword(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public ChangePasswordRequest setVirtualHost(String host) {
+ virtualHost = host;
+ return this;
+ }
+
+ public boolean isDryRun() {
+ return dryRun;
+ }
+
+ public void setDryRun(boolean dryRun) {
+ this.dryRun = dryRun;
+ }
+
+ public AuthToken getAuthToken() { return authToken; }
+ public ChangePasswordRequest setAuthToken(AuthToken authToken) { this.authToken = authToken; return this; }
+}
diff --git a/soap/src/java/com/zimbra/soap/admin/message/ChangePasswordResponse.java b/soap/src/java/com/zimbra/soap/admin/message/ChangePasswordResponse.java
new file mode 100644
index 00000000000..19d8770420b
--- /dev/null
+++ b/soap/src/java/com/zimbra/soap/admin/message/ChangePasswordResponse.java
@@ -0,0 +1,65 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Zimbra Collaboration Suite Server
+ * Copyright (C) 2024 Synacor, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software Foundation,
+ * version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ * ***** END LICENSE BLOCK *****
+ */
+package com.zimbra.soap.admin.message;
+
+import com.zimbra.common.gql.GqlConstants;
+import com.zimbra.common.soap.AccountConstants;
+import com.zimbra.common.soap.AdminConstants;
+import com.zimbra.soap.json.jackson.annotate.ZimbraJsonAttribute;
+import io.leangen.graphql.annotations.GraphQLQuery;
+import io.leangen.graphql.annotations.types.GraphQLType;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name= AdminConstants.E_CHANGE_PASSWORD_RESPONSE)
+@GraphQLType(name= GqlConstants.CLASS_CHANGE_PASSWORD_RESPONSE, description="The response to change password request.")
+@XmlType(propOrder = {})
+public class ChangePasswordResponse {
+
+ /**
+ * @zm-api-field-tag new-auth-token
+ * @zm-api-field-description New authToken, as old authToken is invalidated on password change.
+ */
+ @XmlElement(name=AccountConstants.E_AUTH_TOKEN /* authToken */, required=true)
+ private String authToken;
+ /**
+ * @zm-api-field-description Life time associated with {new-auth-token}
+ */
+ @ZimbraJsonAttribute
+ @XmlElement(name=AccountConstants.E_LIFETIME /* lifetime */, required=true)
+ private long lifetime;
+
+ public ChangePasswordResponse() {
+ }
+
+ @GraphQLQuery(name=GqlConstants.AUTH_TOKEN, description="Auth token based on the new password")
+ public String getAuthToken() { return authToken; }
+ @GraphQLQuery(name=GqlConstants.LIFETIME, description="Life time of the auth token")
+ public long getLifetime() { return lifetime; }
+
+ public ChangePasswordResponse setAuthToken(String authToken) {
+ this.authToken = authToken;
+ return this;
+ }
+
+ public ChangePasswordResponse setLifetime(long lifetime) {
+ this.lifetime = lifetime;
+ return this;
+ }
+}
diff --git a/store/src/java/com/zimbra/cs/service/account/AccountDocumentHandler.java b/store/src/java/com/zimbra/cs/service/account/AccountDocumentHandler.java
index 5aa3cbc61ac..7fe49050532 100644
--- a/store/src/java/com/zimbra/cs/service/account/AccountDocumentHandler.java
+++ b/store/src/java/com/zimbra/cs/service/account/AccountDocumentHandler.java
@@ -50,34 +50,7 @@ protected Element proxyIfNecessary(Element request, Map context)
throw e;
}
}
-
- /*
- * bug 27389
- */
- protected boolean checkPasswordSecurity(Map context) throws ServiceException {
- HttpServletRequest req = (HttpServletRequest)context.get(SoapServlet.SERVLET_REQUEST);
- boolean isHttps = req.getScheme().equals("https");
- if (isHttps)
- return true;
-
- // clear text
- Server server = Provisioning.getInstance().getLocalServer();
- String modeString = server.getAttr(Provisioning.A_zimbraMailMode, null);
- if (modeString == null) {
- // not likely, but just log and let it through
- ZimbraLog.soap.warn("missing " + Provisioning.A_zimbraMailMode +
- " for checking password security, allowing the request");
- return true;
- }
-
- MailMode mailMode = Provisioning.MailMode.fromString(modeString);
- if (mailMode == MailMode.mixed &&
- !server.getBooleanAttr(Provisioning.A_zimbraMailClearTextPasswordEnabled, true))
- return false;
- else
- return true;
- }
-
+
protected Set getReqAttrs(Element request, AttributeClass klass) throws ServiceException {
String attrsStr = request.getAttribute(AccountConstants.A_ATTRS, null);
if (attrsStr == null) {
diff --git a/store/src/java/com/zimbra/cs/service/account/Auth.java b/store/src/java/com/zimbra/cs/service/account/Auth.java
index 6aedd458322..0176f427b1e 100644
--- a/store/src/java/com/zimbra/cs/service/account/Auth.java
+++ b/store/src/java/com/zimbra/cs/service/account/Auth.java
@@ -499,7 +499,7 @@ private Element needTwoFactorAuth(Map context, Element requestEl
private Element needResetPassword(Map context, Element requestElement, Account account, TwoFactorAuth auth,
ZimbraSoapContext zsc, TokenType tokenType) throws ServiceException {
Element response = zsc.createElement(AccountConstants.AUTH_RESPONSE);
- AuthToken authToken = AuthProvider.getAuthToken(account, Usage.RESET_PASSWORD , tokenType);
+ AuthToken authToken = AuthProvider.getAuthToken(account, Usage.RESET_PASSWORD, tokenType);
response.addAttribute(AccountConstants.E_LIFETIME, authToken.getExpires() - System.currentTimeMillis(), Element.Disposition.CONTENT);
response.addUniqueElement(AccountConstants.E_RESET_PWD).setText("true");
authToken.encodeAuthResp(response, false);
diff --git a/store/src/java/com/zimbra/cs/service/admin/AdminDocumentHandler.java b/store/src/java/com/zimbra/cs/service/admin/AdminDocumentHandler.java
index a41784bc7c4..a55c0249df0 100644
--- a/store/src/java/com/zimbra/cs/service/admin/AdminDocumentHandler.java
+++ b/store/src/java/com/zimbra/cs/service/admin/AdminDocumentHandler.java
@@ -57,6 +57,7 @@
import com.zimbra.cs.account.names.NameUtil;
import com.zimbra.cs.session.Session;
import com.zimbra.soap.DocumentHandler;
+import com.zimbra.soap.SoapServlet;
import com.zimbra.soap.ZimbraSoapContext;
import com.zimbra.soap.admin.type.CosSelector;
import com.zimbra.soap.admin.type.CosSelector.CosBy;
@@ -64,6 +65,8 @@
import com.zimbra.soap.admin.type.ServerSelector;
import com.zimbra.soap.type.AccountSelector;
+import javax.servlet.http.HttpServletRequest;
+
/**
* @since Oct 4, 2004
* @author schemers
diff --git a/store/src/java/com/zimbra/cs/service/admin/AdminService.java b/store/src/java/com/zimbra/cs/service/admin/AdminService.java
index 09413c2bb93..918f2f7cca9 100644
--- a/store/src/java/com/zimbra/cs/service/admin/AdminService.java
+++ b/store/src/java/com/zimbra/cs/service/admin/AdminService.java
@@ -58,6 +58,7 @@ public void registerHandlers(DocumentDispatcher dispatcher) {
dispatcher.registerHandler(AdminConstants.REMOVE_ACCOUNT_ALIAS_REQUEST, new RemoveAccountAlias());
dispatcher.registerHandler(AdminConstants.SEARCH_ACCOUNTS_REQUEST, new SearchAccounts());
dispatcher.registerHandler(AdminConstants.RENAME_ACCOUNT_REQUEST, new RenameAccount());
+ dispatcher.registerHandler(AdminConstants.CHANGE_PASSWORD_REQUEST, new ChangePassword());
dispatcher.registerHandler(AdminConstants.CHANGE_PRIMARY_EMAIL_REQUEST, new ChangePrimaryEmail());
dispatcher.registerHandler(AdminConstants.RESET_ACCOUNT_PASSWORD_REQUEST, new ResetAccountPassword());
diff --git a/store/src/java/com/zimbra/cs/service/admin/Auth.java b/store/src/java/com/zimbra/cs/service/admin/Auth.java
index 6efc5f7ed6a..134a7f2f48c 100644
--- a/store/src/java/com/zimbra/cs/service/admin/Auth.java
+++ b/store/src/java/com/zimbra/cs/service/admin/Auth.java
@@ -105,11 +105,17 @@ public Element handle(Element request, Map context) throws Servi
Map authCtxt = AccountUtil.getAdminAuthContext(context, acctName, zsc);
context.put(Provisioning.AUTH_MODE_KEY, AuthMode.PASSWORD);
-
- prov.authAccount(acct, password, AuthContext.Protocol.soap, authCtxt);
+ Usage usage = Usage.AUTH;
+ try {
+ prov.authAccount(acct, password, AuthContext.Protocol.soap, authCtxt);
+ } catch (AccountServiceException ase) {
+ if (AccountServiceException.CHANGE_PASSWORD.equals(ase.getCode())) {
+ ZimbraLog.account.info("zimbraPasswordMustChange is enabled so creating a auth-token used to change password.");
+ usage = Usage.RESET_PASSWORD;
+ }
+ }
AuthMech authedByMech = (AuthMech) authCtxt.get(AuthContext.AC_AUTHED_BY_MECH);
- Usage usage = Usage.AUTH;
TokenType tokenType = null;
if (AccountUtil.isTwoFactorAccount(acct)) {
@@ -157,7 +163,9 @@ private Element doResponse(Element request, AuthToken at, ZimbraSoapContext zsc,
Element response = zsc.createElement(AdminConstants.AUTH_RESPONSE);
response.addAttribute(AdminConstants.E_LIFETIME, at.getExpires() - System.currentTimeMillis(), Element.Disposition.CONTENT);
at.setCsrfTokenEnabled(csrfSupport);
- if (at.getUsage() == Usage.TWO_FACTOR_AUTH) {
+ if (at.getUsage() == Usage.RESET_PASSWORD) {
+ response.addUniqueElement(AccountConstants.E_RESET_PWD).setText("true");
+ } else if (at.getUsage() == Usage.TWO_FACTOR_AUTH) {
response.addUniqueElement(AccountConstants.E_TWO_FACTOR_AUTH_REQUIRED).setText("true");
AccountUtil.addTwoFactorAttributes(response, acct);
} else {
diff --git a/store/src/java/com/zimbra/cs/service/admin/ChangePassword.java b/store/src/java/com/zimbra/cs/service/admin/ChangePassword.java
new file mode 100644
index 00000000000..d9f282f6868
--- /dev/null
+++ b/store/src/java/com/zimbra/cs/service/admin/ChangePassword.java
@@ -0,0 +1,134 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ * Zimbra Collaboration Suite Server
+ * Copyright (C) 2024 Synacor, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software Foundation,
+ * version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License along with this program.
+ * If not, see .
+ * ***** END LICENSE BLOCK *****
+ */
+package com.zimbra.cs.service.admin;
+
+import com.zimbra.common.account.Key;
+import com.zimbra.common.service.ServiceException;
+import com.zimbra.common.soap.AccountConstants;
+import com.zimbra.common.soap.AdminConstants;
+import com.zimbra.common.soap.Element;
+import com.zimbra.common.util.StringUtil;
+import com.zimbra.cs.account.Account;
+import com.zimbra.cs.account.AccountServiceException;
+import com.zimbra.cs.account.AuthToken;
+import com.zimbra.cs.account.AuthTokenException;
+import com.zimbra.cs.account.Domain;
+import com.zimbra.cs.account.Provisioning;
+import com.zimbra.cs.service.AuthProvider;
+import com.zimbra.soap.ZimbraSoapContext;
+
+import java.util.Map;
+
+public class ChangePassword extends AdminDocumentHandler {
+ @Override
+ public Element handle(Element request, Map context) throws ServiceException {
+ if (!checkPasswordSecurity(context)) {
+ throw ServiceException.INVALID_REQUEST("clear text password is not allowed", null);
+ }
+
+ ZimbraSoapContext zsc = getZimbraSoapContext(context);
+ Element authTokenEl = request.getOptionalElement(AccountConstants.E_AUTH_TOKEN);
+ if (authTokenEl == null && zsc.getAuthToken() == null) {
+ throw ServiceException.INVALID_REQUEST("invalid request parameter", null);
+ }
+
+ String namePassedIn = request.getAttribute(AccountConstants.E_ACCOUNT);
+ String name = namePassedIn;
+
+ Element virtualHostEl = request.getOptionalElement(AccountConstants.E_VIRTUAL_HOST);
+ String virtualHost = virtualHostEl == null ? null : virtualHostEl.getText().toLowerCase();
+
+ Provisioning prov = Provisioning.getInstance();
+ if (virtualHost != null && name.indexOf('@') == -1) {
+ Domain d = prov.get(Key.DomainBy.virtualHostname, virtualHost);
+ if (d != null) {
+ name = name + "@" + d.getName();
+ }
+ }
+
+ String text = request.getAttribute(AccountConstants.E_DRYRUN, null);
+ boolean dryRun = false;
+ if (!StringUtil.isNullOrEmpty(text)) {
+ if (text.equals("1") || text.equalsIgnoreCase("true")) {
+ dryRun = true;
+ }
+ }
+
+ AuthToken at = zsc.getAuthToken();
+ Account acct = prov.get(Key.AccountBy.name, name, at);
+ if (acct == null) {
+ throw AccountServiceException.AuthFailedServiceException.AUTH_FAILED(name, namePassedIn, "account not found");
+ }
+
+ AuthToken.Usage usage = AuthToken.Usage.AUTH;
+ if (authTokenEl != null) {
+ try {
+ at = AuthProvider.getAuthToken(authTokenEl, acct);
+ } catch (AuthTokenException e) {
+ throw ServiceException.AUTH_REQUIRED();
+ }
+ if (at == null) {
+ throw ServiceException.AUTH_REQUIRED("invalid auth token");
+ }
+ usage = AuthToken.Usage.RESET_PASSWORD;
+ } else if (!canAccessAccount(zsc, acct)) {
+ throw ServiceException.PERM_DENIED("cannot access account");
+ }
+
+ acct = AuthProvider.validateAuthToken(prov, at, false, usage);
+ if (acct == null) {
+ throw AccountServiceException.AuthFailedServiceException.AUTH_FAILED(name, namePassedIn, "account not found");
+ }
+ String oldPassword = request.getAttribute(AccountConstants.E_OLD_PASSWORD);
+ String newPassword = request.getAttribute(AccountConstants.E_PASSWORD);
+
+ boolean mustChange = acct.getBooleanAttr(Provisioning.A_zimbraPasswordMustChange, false);
+ if (mustChange && AuthToken.Usage.RESET_PASSWORD == at.getUsage()) {
+ prov.changePassword(acct, oldPassword, newPassword, dryRun);
+ try {
+ at.deRegister();
+ } catch (AuthTokenException e) {
+ throw ServiceException.FAILURE("cannot de-register reset password auth token", e);
+ }
+ } else if (acct.isIsExternalVirtualAccount() && StringUtil.isNullOrEmpty(oldPassword)
+ && !acct.isVirtualAccountInitialPasswordSet() && acct.getId().equals(zsc.getAuthtokenAccountId())) {
+ prov.setPassword(acct, newPassword, true);
+ acct.setVirtualAccountInitialPasswordSet(true);
+ } else {
+ prov.changePassword(acct, oldPassword, newPassword, dryRun);
+ }
+
+ Element response = zsc.createElement(AdminConstants.CHANGE_PASSWORD_RESPONSE);
+ if (!dryRun) {
+ at = AuthProvider.getAuthToken(acct);
+ at.encodeAuthResp(response, true);
+ response.addAttribute(AccountConstants.E_LIFETIME, at.getExpires() - System.currentTimeMillis(), Element.Disposition.CONTENT);
+ }
+
+ return response;
+ }
+
+ @Override
+ public boolean needsAdminAuth(Map context) {
+ return false;
+ }
+
+ @Override
+ public boolean needsAuth(Map context) {
+ return false;
+ }
+}
diff --git a/store/src/java/com/zimbra/cs/servlet/util/AuthUtil.java b/store/src/java/com/zimbra/cs/servlet/util/AuthUtil.java
index e12f0a11f1b..205c37f87ef 100644
--- a/store/src/java/com/zimbra/cs/servlet/util/AuthUtil.java
+++ b/store/src/java/com/zimbra/cs/servlet/util/AuthUtil.java
@@ -391,7 +391,7 @@ public static AuthToken getAuthToken(Element request, ZimbraSoapContext zsc)
public static AuthToken getAuthToken(Account acct, boolean isAdmin, Usage usage, TokenType tokenType,
AuthMech authedByMech) throws AuthProviderException {
AuthToken at = null;
- if (Usage.TWO_FACTOR_AUTH == usage) {
+ if (Usage.TWO_FACTOR_AUTH == usage || Usage.RESET_PASSWORD == usage) {
at = AuthProvider.getAuthToken(acct, isAdmin, usage, tokenType);
} else {
at = AuthProvider.getAuthToken(acct, isAdmin, authedByMech);
diff --git a/store/src/java/com/zimbra/soap/DocumentHandler.java b/store/src/java/com/zimbra/soap/DocumentHandler.java
index 952d1170c37..2eec72a3cf2 100644
--- a/store/src/java/com/zimbra/soap/DocumentHandler.java
+++ b/store/src/java/com/zimbra/soap/DocumentHandler.java
@@ -709,4 +709,29 @@ public static void resetLocalHost() {
LOCAL_HOST = "";
LOCAL_HOST_ID = "";
}
+
+ protected boolean checkPasswordSecurity(Map context) throws ServiceException {
+ HttpServletRequest req = (HttpServletRequest)context.get(SoapServlet.SERVLET_REQUEST);
+ boolean isHttps = req.getScheme().equals("https");
+ if (isHttps) {
+ return true;
+ }
+
+ Server server = Provisioning.getInstance().getLocalServer();
+ String modeString = server.getAttr(Provisioning.A_zimbraMailMode, null);
+ if (modeString == null) {
+ // not likely, but just log and let it through
+ ZimbraLog.soap.warn("missing " + Provisioning.A_zimbraMailMode +
+ " for checking password security, allowing the request");
+ return true;
+ }
+
+ Provisioning.MailMode mailMode = Provisioning.MailMode.fromString(modeString);
+ if (mailMode == Provisioning.MailMode.mixed &&
+ !server.getBooleanAttr(Provisioning.A_zimbraMailClearTextPasswordEnabled, true)) {
+ return false;
+ } else {
+ return true;
+ }
+ }
}