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; + } + } }