Author: nmalin
Date: Sat Sep 21 19:12:23 2019 New Revision: 1867296 URL: http://svn.apache.org/viewvc?rev=1867296&view=rev Log: Fixed: Any ecommerce user has the ability to reset anothers password (including admin) via 'Forget Your Password' (OFBIZ-4361) Trunk backport r1866478 and r1866518 Currently, any user (via ecommerce 'Forget Your Password') has the ability to reset another users password, including 'admin' without permission. By simply entering 'admin' and clicking 'Email Password', the following is displayed: The following occurred: A new password has been created and sent to you. Please check your Email. This now forces the user of the ERP to change their password. It is also possible to generate a dictionary attack against ofbiz because there is no capta code required. This is serious security risk. I have modified the patch following comments I made in the Jira, notably Removed unused Java variables Removed a check in LoginEvents::forgotPassword which prevented to show error messages Changed fr and en SecurityExtPasswordSentToYou + SecurityExtThisEmailIsInResponseToYourRequestToHave labels + template PasswordEmail.ftl + loginservices.token_incorrect labels Added fr and en SecurityExtIgnoreEmail + SecurityExtLinkOnce labels Removed changes in general.properties I did not remove the 2 GetSecurityQuestion.ftl files (webpos one was still in) There is still room for improvement. I'll discuss them on the Jira and dev ML. But this version is already strong enough to not wait that the patch is inapplicable! Thanks: mz4wheeler (Mike Z) for the Jira, Nicolas Malin for the patch, I guess with some Gil's help, and all others for comments and ideas Modified: ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/config/EmailPasswordUiLabels.xml ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/src/main/java/org/apache/ofbiz/securityext/login/LoginEvents.java ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/template/email/PasswordEmail.ftl ofbiz/ofbiz-framework/branches/release18.12/build.gradle ofbiz/ofbiz-framework/branches/release18.12/framework/common/config/SecurityextUiLabels.xml ofbiz/ofbiz-framework/branches/release18.12/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java ofbiz/ofbiz-framework/branches/release18.12/framework/common/webcommon/WEB-INF/common-controller.xml ofbiz/ofbiz-framework/branches/release18.12/framework/common/widget/CommonScreens.xml ofbiz/ofbiz-framework/branches/release18.12/framework/security/config/security.properties ofbiz/ofbiz-framework/branches/release18.12/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java ofbiz/ofbiz-framework/branches/release18.12/framework/service/servicedef/services.xml ofbiz/ofbiz-framework/branches/release18.12/framework/service/src/main/java/org/apache/ofbiz/service/ServiceDispatcher.java ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ChangePassword.ftl ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ForgotPassword.ftl ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/Login.ftl ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/widget/CommonScreens.xml ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/widget/Theme.xml ofbiz/ofbiz-framework/branches/release18.12/themes/rainbowstone/template/ChangePassword.ftl ofbiz/ofbiz-framework/branches/release18.12/themes/rainbowstone/template/ForgotPassword.ftl ofbiz/ofbiz-framework/branches/release18.12/themes/rainbowstone/template/Login.ftl ofbiz/ofbiz-framework/branches/release18.12/themes/rainbowstone/widget/Theme.xml Modified: ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/config/EmailPasswordUiLabels.xml URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/config/EmailPasswordUiLabels.xml?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/config/EmailPasswordUiLabels.xml (original) +++ ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/config/EmailPasswordUiLabels.xml Sat Sep 21 19:12:23 2019 @@ -33,36 +33,18 @@ <value xml:lang="zh">ä¸ä¸ªæ°ç</value> <value xml:lang="zh-TW">ä¸åæ°ç</value> </property> - <property key="SecurityExtNewPasswordMssgEncryptionOff"> - <value xml:lang="da">Deres adgangskode er:</value> - <value xml:lang="de">Ihr Passwort ist: </value> - <value xml:lang="en">Your password is :- </value> - <value xml:lang="fr">Votre mot de passe est : </value> - <value xml:lang="it">La tua password è :- </value> - <value xml:lang="ja">ããªãã®ãã¹ã¯ã¼ã㯠:-</value> - <value xml:lang="nl">Uw wachtwoord is :</value> - <value xml:lang="pt-BR">Sua senha é:</value> - <value xml:lang="zh">ä½ çå¯ç æ¯ï¼- </value> - <value xml:lang="vi">Máºt khẩu của bạn :-</value> - <value xml:lang="zh-TW">ä½ çå¯ç¢¼æ¯ :- </value> + <property key="SecurityExtIgnoreEmail"> + <value xml:lang="en">Please ignore this email if you did not request a password change</value> + <value xml:lang="fr">Veuillez ignorer ce courriel si vous n'avez pas demandé un changement de mot de passe</value> </property> - <property key="SecurityExtNewPasswordMssgEncryptionOn"> - <value xml:lang="da">Deres nye adgangskode er :- </value> - <value xml:lang="de">Ihr neues Passwort ist : </value> - <value xml:lang="en">Your new password is :- </value> - <value xml:lang="fr">Votre nouveau mot de passe est : </value> - <value xml:lang="it">La nuova password è :- </value> - <value xml:lang="ja">ããªãã®æ°ãããã¹ã¯ã¼ã㯠:-</value> - <value xml:lang="nl">Uw nieuwe wachtwoord is : </value> - <value xml:lang="pt-BR">Sua nova senha é:</value> - <value xml:lang="zh">ä½ çæ°å¯ç æ¯ï¼- </value> - <value xml:lang="zh-TW">ä½ çæ°å¯ç¢¼æ¯ :- </value> - <value xml:lang="vi">Máºt khẩu má»i của bạn :-</value> + <property key="SecurityExtLinkOnce"> + <value xml:lang="en">This link can be used only once</value> + <value xml:lang="fr">Ce lien ne peut être utilisé qu'une seule fois</value> </property> <property key="SecurityExtPasswordSentToYou"> <value xml:lang="da">kodeord er sendt til Dem</value> <value xml:lang="de">Passwort das Ihnen zugesendet wurde</value> - <value xml:lang="en">password sent to you</value> + <value xml:lang="en">password</value> <value xml:lang="fr">mot de passe</value> <value xml:lang="it">password inviata a te</value> <value xml:lang="ja">ãã¹ã¯ã¼ããéä¿¡ãã¾ãã</value> Modified: ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/src/main/java/org/apache/ofbiz/securityext/login/LoginEvents.java URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/src/main/java/org/apache/ofbiz/securityext/login/LoginEvents.java?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/src/main/java/org/apache/ofbiz/securityext/login/LoginEvents.java (original) +++ ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/src/main/java/org/apache/ofbiz/securityext/login/LoginEvents.java Sat Sep 21 19:12:23 2019 @@ -20,39 +20,32 @@ package org.apache.ofbiz.securityext.login; import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import org.apache.commons.lang.RandomStringUtils; -import org.apache.ofbiz.base.crypto.HashCrypt; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.GeneralException; -import org.apache.ofbiz.base.util.UtilFormatOut; import org.apache.ofbiz.base.util.UtilHttp; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; -import org.apache.ofbiz.common.login.LoginServices; import org.apache.ofbiz.entity.Delegator; -import org.apache.ofbiz.entity.GenericDelegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; -import org.apache.ofbiz.entity.model.ModelField.EncryptMethod; -import org.apache.ofbiz.entity.util.EntityCrypto; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityUtilProperties; import org.apache.ofbiz.party.contact.ContactHelper; import org.apache.ofbiz.product.product.ProductEvents; import org.apache.ofbiz.product.store.ProductStoreWorker; -import org.apache.ofbiz.service.GenericServiceException; +import org.apache.ofbiz.security.SecurityUtil; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; @@ -66,7 +59,6 @@ public class LoginEvents { public static final String module = LoginEvents.class.getName(); public static final String resource = "SecurityextUiLabels"; public static final String usernameCookieName = "OFBiz.Username"; - private static final String keyValue = UtilProperties.getPropertyValue(LoginWorker.securityProperties, "login.secret_key_string"); /** * Save USERNAME and PASSWORD for use by auth pages even if we start in non-auth pages. * @@ -118,38 +110,13 @@ public class LoginEvents { * @return String specifying the exit status of this event */ public static String forgotPassword(HttpServletRequest request, HttpServletResponse response) { - GenericDelegator delegator = (GenericDelegator) request.getAttribute("delegator"); - String questionEnumId = request.getParameter("securityQuestion"); - String securityAnswer = request.getParameter("securityAnswer"); - String userLoginId = request.getParameter("USERNAME"); - String errMsg = null; - - try { - GenericValue userLoginSecurityQuestion = EntityQuery.use(delegator).from("UserLoginSecurityQuestion").where("questionEnumId", questionEnumId, "userLoginId", userLoginId).cache().queryOne(); - if (userLoginSecurityQuestion != null) { - if (UtilValidate.isEmpty(securityAnswer)) { - errMsg = UtilProperties.getMessage(resource, "loginservices.security_answer_empty", UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); - return "error"; - } - String ulSecurityAnswer = userLoginSecurityQuestion.getString("securityAnswer"); - if (UtilValidate.isNotEmpty(ulSecurityAnswer) && !securityAnswer.equalsIgnoreCase(ulSecurityAnswer)) { - errMsg = UtilProperties.getMessage(resource, "loginservices.security_answer_not_match", UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); - return "error"; - } - } - } catch (GenericEntityException e) { - errMsg = UtilProperties.getMessage(resource, "loginevents.problem_getting_security_question_record", UtilHttp.getLocale(request)); - Debug.logError(e, errMsg, module); - } - if ((UtilValidate.isNotEmpty(request.getParameter("GET_PASSWORD_HINT"))) || (UtilValidate.isNotEmpty(request.getParameter("GET_PASSWORD_HINT.x")))) { + if (UtilValidate.isNotEmpty(request.getParameter("GET_PASSWORD_HINT")) || UtilValidate.isNotEmpty(request.getParameter("GET_PASSWORD_HINT.x"))) { return showPasswordHint(request, response); } else if ((UtilValidate.isNotEmpty(request.getParameter("EMAIL_PASSWORD"))) || (UtilValidate.isNotEmpty(request.getParameter("EMAIL_PASSWORD.x")))) { - return emailPassword(request, response); - } else { - return "success"; + return emailPasswordRequest(request, response); } + + return "success"; } /** Show the password hint for the userLoginId specified in the request object. @@ -174,24 +141,47 @@ public class LoginEvents { return "error"; } - GenericValue supposedUserLogin = null; + String questionEnumId = request.getParameter("securityQuestion"); + String securityAnswer = request.getParameter("securityAnswer"); + if (UtilValidate.isEmpty(questionEnumId) && UtilValidate.isEmpty(securityAnswer)) { + return "success"; + } try { + GenericValue userLoginSecurityQuestion = EntityQuery.use(delegator).from("UserLoginSecurityQuestion").where("questionEnumId", questionEnumId, "userLoginId", userLoginId).cache().queryOne(); + if (userLoginSecurityQuestion != null) { + if (UtilValidate.isEmpty(securityAnswer)) { + errMsg = UtilProperties.getMessage(resource, "loginservices.security_answer_empty", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + String ulSecurityAnswer = userLoginSecurityQuestion.getString("securityAnswer"); + if (UtilValidate.isNotEmpty(ulSecurityAnswer) && ! securityAnswer.equalsIgnoreCase(ulSecurityAnswer)) { + errMsg = UtilProperties.getMessage(resource, "loginservices.security_answer_not_match", UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; + } + } + } catch (GenericEntityException e) { + errMsg = UtilProperties.getMessage(resource, "loginevents.problem_getting_security_question_record", UtilHttp.getLocale(request)); + Debug.logError(e, errMsg, module); + } + + GenericValue supposedUserLogin = null; + String passwordHint = null; + + try { supposedUserLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); } catch (GenericEntityException gee) { Debug.logWarning(gee, "", module); } - if (supposedUserLogin == null) { - // the Username was not found - errMsg = UtilProperties.getMessage(resource, "loginevents.username_not_found_reenter", UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); - return "error"; + + if (supposedUserLogin != null) { + passwordHint = supposedUserLogin.getString("passwordHint"); } - String passwordHint = supposedUserLogin.getString("passwordHint"); - - if (UtilValidate.isEmpty(passwordHint)) { - // the Username was not found + if (supposedUserLogin == null || UtilValidate.isEmpty(passwordHint)) { + // the Username was not found or there was no hint for the Username errMsg = UtilProperties.getMessage(resource, "loginevents.no_password_hint_specified_try_password_emailed", UtilHttp.getLocale(request)); request.setAttribute("_ERROR_MESSAGE_", errMsg); return "error"; @@ -200,162 +190,128 @@ public class LoginEvents { Map<String, String> messageMap = UtilMisc.toMap("passwordHint", passwordHint); errMsg = UtilProperties.getMessage(resource, "loginevents.password_hint_is", messageMap, UtilHttp.getLocale(request)); request.setAttribute("_EVENT_MESSAGE_", errMsg); - return "success"; + return "auth"; } /** - * Email the password for the userLoginId specified in the request object. + * event to send an email with a link to change password * * @param request The HTTPRequest object for the current request * @param response The HTTPResponse object for the current request * @return String specifying the exit status of this event */ - public static String emailPassword(HttpServletRequest request, HttpServletResponse response) { - String defaultScreenLocation = "component://securityext/widget/EmailSecurityScreens.xml#PasswordEmail"; - + public static String emailPasswordRequest(HttpServletRequest request, HttpServletResponse response) { Delegator delegator = (Delegator) request.getAttribute("delegator"); LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher"); String productStoreId = ProductStoreWorker.getProductStoreId(request); + String defaultScreenLocation = "component://securityext/widget/EmailSecurityScreens.xml#PasswordEmail"; - String errMsg = null; - - boolean useEncryption = "true".equals(EntityUtilProperties.getPropertyValue("security", "password.encrypt", delegator)); - + // get userloginId String userLoginId = request.getParameter("USERNAME"); - - if ((userLoginId != null) && ("true".equals(EntityUtilProperties.getPropertyValue("security", "username.lowercase", delegator)))) { - userLoginId = userLoginId.toLowerCase(Locale.getDefault()); - } - if (UtilValidate.isEmpty(userLoginId)) { - // the password was incomplete - errMsg = UtilProperties.getMessage(resource, "loginevents.username_was_empty_reenter", UtilHttp.getLocale(request)); + String errMsg = UtilProperties.getMessage(resource, "loginevents.username_was_empty_reenter", + UtilHttp.getLocale(request)); request.setAttribute("_ERROR_MESSAGE_", errMsg); return "error"; } + if (UtilValidate.isNotEmpty(request.getParameter("token"))) { + return "success"; + } - GenericValue supposedUserLogin = null; - String passwordToSend = null; - String autoPassword = null; + GenericValue userLogin; try { - supposedUserLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); - if (supposedUserLogin == null) { - // the Username was not found - errMsg = UtilProperties.getMessage(resource, "loginevents.username_not_found_reenter", UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); + // test if user exist and is active + userLogin = EntityQuery.use(delegator) + .from("UserLogin") + .where("userLoginId", userLoginId) + .queryOne(); + if (userLogin == null || "N".equals(userLogin.getString("enabled"))) { + Debug.logError("userlogin uknown or disabled " + userLogin, module); + //giving a "sent email to associated email-address" response, to suppress feedback on in-/valid usernames + String errMsg = UtilProperties.getMessage(resource, "loginevents.new_password_sent_check_email", + UtilHttp.getLocale(request)); + request.setAttribute("_EVENT_MESSAGE_", errMsg); + return "success"; + } + + // check login is associated to a party + GenericValue userParty = userLogin.getRelatedOne("Party", false); + if (userParty == null) { + String errMsg = UtilProperties.getMessage(resource, "loginevents.username_not_found_reenter", + UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); return "error"; } - if (useEncryption) { - // password encrypted, can't send, generate new password and email to user - passwordToSend = RandomStringUtils.randomAlphanumeric(EntityUtilProperties.getPropertyAsInteger("security", "password.length.min", 5)); - if ("true".equals(EntityUtilProperties.getPropertyValue("security", "password.lowercase", delegator))){ - passwordToSend=passwordToSend.toLowerCase(Locale.getDefault()); - } - autoPassword = RandomStringUtils.randomAlphanumeric(EntityUtilProperties.getPropertyAsInteger("security", "password.length.min", 5)); - EntityCrypto entityCrypto = new EntityCrypto(delegator,null); - try { - passwordToSend = entityCrypto.encrypt(keyValue, EncryptMethod.TRUE, autoPassword); - } catch (GeneralException e) { - Debug.logWarning(e, "Problem in encryption", module); - } - supposedUserLogin.set("currentPassword", HashCrypt.cryptUTF8(LoginServices.getHashType(), null, autoPassword)); - supposedUserLogin.set("passwordHint", "Auto-Generated Password"); - if ("true".equals(EntityUtilProperties.getPropertyValue("security", "password.email_password.require_password_change", delegator))){ - supposedUserLogin.set("requirePasswordChange", "Y"); - } - } else { - passwordToSend = supposedUserLogin.getString("currentPassword"); - } - /* Its a Base64 string, it can contain + and this + will be converted to space after decoding the url. - For example: passwordToSend "DGb1s2wgUQmwOBK9FK+fvQ==" will be converted to "DGb1s2wgUQmwOBK9FK fvQ==" - So to fix it, done Url encoding of passwordToSend. - */ - passwordToSend = URLEncoder.encode(passwordToSend, "UTF-8"); - } catch (GenericEntityException | UnsupportedEncodingException e) { - Debug.logWarning(e, "", module); - Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.toString()); - errMsg = UtilProperties.getMessage(resource, "loginevents.error_accessing_password", messageMap, UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); - return "error"; - } - StringBuilder emails = new StringBuilder(); - GenericValue party = null; - - try { - party = supposedUserLogin.getRelatedOne("Party", false); - } catch (GenericEntityException e) { - Debug.logWarning(e, "", module); - } - if (party != null) { - Iterator<GenericValue> emailIter = UtilMisc.toIterator(ContactHelper.getContactMechByPurpose(party, "PRIMARY_EMAIL", false)); - while (emailIter != null && emailIter.hasNext()) { - GenericValue email = emailIter.next(); - emails.append(emails.length() > 0 ? "," : "").append(email.getString("infoString")); + // check there is an email to send to + List<GenericValue> contactMechs = (List<GenericValue>) ContactHelper.getContactMechByPurpose(userParty, "PRIMARY_EMAIL", false); + if (UtilValidate.isEmpty(contactMechs)) { + // the email was not found + String errMsg = UtilProperties.getMessage(resource, + "loginevents.no_primary_email_address_set_contact_customer_service", + UtilHttp.getLocale(request)); + request.setAttribute("_ERROR_MESSAGE_", errMsg); + return "error"; } - } + String emails = contactMechs.stream() + .map(email -> email.getString("infoString")) + .collect(Collectors.joining(",")); - if (UtilValidate.isEmpty(emails.toString())) { - // the Username was not found - errMsg = UtilProperties.getMessage(resource, "loginevents.no_primary_email_address_set_contact_customer_service", UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); - return "error"; - } + //Generate a JWT with default retention time + String jwtToken = SecurityUtil.generateJwtToAuthenticateUserLogin(delegator, userLoginId); - // get the ProductStore email settings - GenericValue productStoreEmail = null; - try { - productStoreEmail = EntityQuery.use(delegator).from("ProductStoreEmailSetting").where("productStoreId", productStoreId, "emailType", "PRDS_PWD_RETRIEVE").queryOne(); - } catch (GenericEntityException e) { - Debug.logError(e, "Problem getting ProductStoreEmailSetting", module); - } - - String bodyScreenLocation = null; - if (productStoreEmail != null) { - bodyScreenLocation = productStoreEmail.getString("bodyScreenLocation"); - } - if (UtilValidate.isEmpty(bodyScreenLocation)) { - bodyScreenLocation = defaultScreenLocation; - } - - // set the needed variables in new context - Map<String, Object> bodyParameters = new HashMap<>(); - bodyParameters.put("useEncryption", useEncryption); - bodyParameters.put("password", UtilFormatOut.checkNull(passwordToSend)); - bodyParameters.put("locale", UtilHttp.getLocale(request)); - bodyParameters.put("userLogin", supposedUserLogin); - bodyParameters.put("productStoreId", productStoreId); - - Map<String, Object> serviceContext = new HashMap<>(); - serviceContext.put("bodyScreenUri", bodyScreenLocation); - serviceContext.put("bodyParameters", bodyParameters); - if (productStoreEmail != null) { - serviceContext.put("subject", productStoreEmail.getString("subject")); - serviceContext.put("sendFrom", productStoreEmail.get("fromAddress")); - serviceContext.put("sendCc", productStoreEmail.get("ccAddress")); - serviceContext.put("sendBcc", productStoreEmail.get("bccAddress")); - serviceContext.put("contentType", productStoreEmail.get("contentType")); - } else { - GenericValue emailTemplateSetting = null; + // get the ProductStore email settings + GenericValue productStoreEmail = null; try { - emailTemplateSetting = EntityQuery.use(delegator).from("EmailTemplateSetting").where("emailTemplateSettingId", "EMAIL_PASSWORD").cache().queryOne(); + productStoreEmail = EntityQuery.use(delegator).from("ProductStoreEmailSetting").where("productStoreId", productStoreId, "emailType", "PRDS_PWD_RETRIEVE").queryOne(); } catch (GenericEntityException e) { - Debug.logError(e, module); + Debug.logError(e, "Problem getting ProductStoreEmailSetting", module); } - if (emailTemplateSetting != null) { - String subject = emailTemplateSetting.getString("subject"); - subject = FlexibleStringExpander.expandString(subject, UtilMisc.toMap("userLoginId", userLoginId)); - serviceContext.put("subject", subject); - serviceContext.put("sendFrom", emailTemplateSetting.get("fromAddress")); + + String bodyScreenLocation = null; + if (productStoreEmail != null) { + bodyScreenLocation = productStoreEmail.getString("bodyScreenLocation"); + } + if (UtilValidate.isEmpty(bodyScreenLocation)) { + bodyScreenLocation = defaultScreenLocation; + } + + // set the needed variables in new context + Map<String, Object> bodyParameters = new HashMap<>(); + bodyParameters.put("token", jwtToken); + bodyParameters.put("locale", UtilHttp.getLocale(request)); + bodyParameters.put("userLogin", userLogin); + bodyParameters.put("productStoreId", productStoreId); + + Map<String, Object> serviceContext = new HashMap<>(); + serviceContext.put("bodyScreenUri", bodyScreenLocation); + serviceContext.put("bodyParameters", bodyParameters); + if (productStoreEmail != null) { + serviceContext.put("subject", productStoreEmail.getString("subject")); + serviceContext.put("sendFrom", productStoreEmail.get("fromAddress")); + serviceContext.put("sendCc", productStoreEmail.get("ccAddress")); + serviceContext.put("sendBcc", productStoreEmail.get("bccAddress")); + serviceContext.put("contentType", productStoreEmail.get("contentType")); } else { - serviceContext.put("subject", UtilProperties.getMessage(resource, "loginservices.password_reminder_subject", UtilMisc.toMap("userLoginId", userLoginId), UtilHttp.getLocale(request))); - serviceContext.put("sendFrom", EntityUtilProperties.getPropertyValue("general", "defaultFromEmailAddress", delegator)); + GenericValue emailTemplateSetting = null; + try { + emailTemplateSetting = EntityQuery.use(delegator).from("EmailTemplateSetting").where("emailTemplateSettingId", "EMAIL_PASSWORD").cache().queryOne(); + } catch (GenericEntityException e) { + Debug.logError(e, module); + } + if (emailTemplateSetting != null) { + String subject = emailTemplateSetting.getString("subject"); + subject = FlexibleStringExpander.expandString(subject, UtilMisc.toMap("userLoginId", userLoginId)); + serviceContext.put("subject", subject); + serviceContext.put("sendFrom", emailTemplateSetting.get("fromAddress")); + } else { + serviceContext.put("subject", UtilProperties.getMessage(resource, "loginservices.password_reminder_subject", UtilMisc.toMap("userLoginId", userLoginId), UtilHttp.getLocale(request))); + serviceContext.put("sendFrom", EntityUtilProperties.getPropertyValue("general", "defaultFromEmailAddress", delegator)); + } } - } - serviceContext.put("sendTo", emails.toString()); - serviceContext.put("partyId", party.getString("partyId")); + serviceContext.put("sendTo", emails); + serviceContext.put("partyId", userParty.getString("partyId")); - try { Map<String, Object> result = dispatcher.runSync("sendMailHiddenInLogFromScreen", serviceContext); if (ServiceUtil.isError(result)) { String errorMessage = ServiceUtil.getErrorMessage(result); @@ -364,39 +320,21 @@ public class LoginEvents { return "error"; } - if (ModelService.RESPOND_ERROR.equals(result.get(ModelService.RESPONSE_MESSAGE))) { + if (ServiceUtil.isError(result)) { Map<String, Object> messageMap = UtilMisc.toMap("errorMessage", result.get(ModelService.ERROR_MESSAGE)); - errMsg = UtilProperties.getMessage(resource, "loginevents.error_unable_email_password_contact_customer_service_errorwas", messageMap, UtilHttp.getLocale(request)); + String errMsg = UtilProperties.getMessage(resource, "loginevents.error_unable_email_password_contact_customer_service_errorwas", messageMap, UtilHttp.getLocale(request)); request.setAttribute("_ERROR_MESSAGE_", errMsg); return "error"; } - } catch (GenericServiceException e) { + } catch (GeneralException e) { Debug.logWarning(e, "", module); - errMsg = UtilProperties.getMessage(resource, "loginevents.error_unable_email_password_contact_customer_service", UtilHttp.getLocale(request)); + String errMsg = UtilProperties.getMessage(resource, "loginevents.error_unable_email_password_contact_customer_service", UtilHttp.getLocale(request)); request.setAttribute("_ERROR_MESSAGE_", errMsg); return "error"; } - // don't save password until after it has been sent - if (useEncryption) { - try { - supposedUserLogin.store(); - } catch (GenericEntityException e) { - Debug.logWarning(e, "", module); - Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.toString()); - errMsg = UtilProperties.getMessage(resource, "loginevents.error_saving_new_password_email_not_correct_password", messageMap, UtilHttp.getLocale(request)); - request.setAttribute("_ERROR_MESSAGE_", errMsg); - return "error"; - } - } - - if (useEncryption) { - errMsg = UtilProperties.getMessage(resource, "loginevents.new_password_createdandsent_check_email", UtilHttp.getLocale(request)); - request.setAttribute("_EVENT_MESSAGE_", errMsg); - } else { - errMsg = UtilProperties.getMessage(resource, "loginevents.new_password_sent_check_email", UtilHttp.getLocale(request)); - request.setAttribute("_EVENT_MESSAGE_", errMsg); - } + String msg = UtilProperties.getMessage(resource, "loginevents.new_password_sent_check_email", UtilHttp.getLocale(request)); + request.setAttribute("_EVENT_MESSAGE_", msg); return "success"; } Modified: ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/template/email/PasswordEmail.ftl URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/template/email/PasswordEmail.ftl?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/template/email/PasswordEmail.ftl (original) +++ ofbiz/ofbiz-framework/branches/release18.12/applications/securityext/template/email/PasswordEmail.ftl Sat Sep 21 19:12:23 2019 @@ -21,12 +21,15 @@ under the License. <head> </head> <body> - <div>${uiLabelMap.SecurityExtThisEmailIsInResponseToYourRequestToHave} <#if useEncryption>${uiLabelMap.SecurityExtANew}<#else>${uiLabelMap.SecurityExtYour}</#if> ${uiLabelMap.SecurityExtPasswordSentToYou}.</div> + <div>${uiLabelMap.SecurityExtThisEmailIsInResponseToYourRequestToHave} ${uiLabelMap.SecurityExtANew} ${uiLabelMap.SecurityExtPasswordSentToYou}.</div> + <div>${uiLabelMap.SecurityExtIgnoreEmail}.</div> + <br /> <div> - <form method="post" action="${baseEcommerceSecureUrl}/partymgr/control/passwordChange?USERNAME=${userLogin.userLoginId!}&password=${password!}&forgotPwdFlag=true&tenantId=${tenantId!}" name="loginform" id="loginform" target="_blank"> + <form method="post" action="${baseEcommerceSecureUrl}/partymgr/control/passwordChange?USERNAME=${userLogin.userLoginId!}&TOKEN=${token!}&forgotPwdFlag=true&tenantId=${tenantId!}" name="loginform" id="loginform" target="_blank"> <input type="submit" name="submit" value="${uiLabelMap.ResetPassword}" /> </form> + ${uiLabelMap.SecurityExtLinkOnce}. </div> </body> </html> Modified: ofbiz/ofbiz-framework/branches/release18.12/build.gradle URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/build.gradle?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/build.gradle (original) +++ ofbiz/ofbiz-framework/branches/release18.12/build.gradle Sat Sep 21 19:12:23 2019 @@ -169,7 +169,7 @@ dependencies { compile 'oro:oro:2.0.8' compile 'wsdl4j:wsdl4j:1.6.3' compile 'org.jsoup:jsoup:1.11.3' - compile 'io.jsonwebtoken:jjwt:0.9.1' + compile 'com.auth0:java-jwt:3.8.2' // ofbiz unit-test compile libs testCompile 'org.mockito:mockito-core:2.23.0' Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/common/config/SecurityextUiLabels.xml URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/common/config/SecurityextUiLabels.xml?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/common/config/SecurityextUiLabels.xml (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/common/config/SecurityextUiLabels.xml Sat Sep 21 19:12:23 2019 @@ -19,6 +19,23 @@ under the License. --> <resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/ofbiz-properties.xsd"> + + <property key="loginevents.change_password_request_error_missing_fields"> + <value xml:lang="en">Error accessing password: ${errorMessage}.</value> + <value xml:lang="es">Merci de renseigner tous les champs </value> + </property> + <property key="loginevents.change_password_request_error_not_valid_parameters"> + <value xml:lang="en">Error accessing password: ${errorMessage}.</value> + <value xml:lang="fr">La demande de changement de mot de passe n'est pas valide.</value> + </property> + <property key="loginevents.change_password_request_error_technical_error"> + <value xml:lang="en">Error accessing password: ${errorMessage}.</value> + <value xml:lang="fr">Une erreur technique c'est produite. La nouveau mot de passe n'a pas été pris en compte</value> + </property> + <property key="loginevents.change_password_request_success"> + <value xml:lang="en">Error accessing password: ${errorMessage}.</value> + <value xml:lang="fr">Le nouveau mot de passe est actif</value> + </property> <property key="loginevents.error_accessing_password"> <value xml:lang="de">Fehler beim Zugriff auf Passwort: ${errorMessage}.</value> <value xml:lang="en">Error accessing password: ${errorMessage}.</value> @@ -844,6 +861,10 @@ <value xml:lang="zh">èª${disabledDateTime}以æ¥ã</value> <value xml:lang="zh-TW">å¾ ${disabledDateTime} éå§.</value> </property> + <property key="loginservices.token_incorrect"> + <value xml:lang="en">Invalid Token</value> + <value xml:lang="fr">Jeton non valide</value> + </property> <property key="loginservices.user_not_found"> <value xml:lang="de">Benutzer konnte nicht gefunden werden</value> <value xml:lang="en">User not found.</value> Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/common/src/main/java/org/apache/ofbiz/common/login/LoginServices.java Sat Sep 21 19:12:23 2019 @@ -36,6 +36,8 @@ import javax.transaction.Transaction; import org.apache.ofbiz.base.crypto.HashCrypt; import org.apache.ofbiz.base.util.Debug; +import org.apache.ofbiz.base.util.StringUtil; +import org.apache.ofbiz.base.util.UtilCodec; import org.apache.ofbiz.base.util.UtilDateTime; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; @@ -61,6 +63,7 @@ import org.apache.ofbiz.service.GenericS import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; +import org.apache.ofbiz.webapp.control.JWTManager; import org.apache.ofbiz.webapp.control.LoginWorker; import org.apache.tomcat.util.res.StringManager; @@ -112,6 +115,10 @@ public class LoginServices { if (password == null) { password = (String) context.get("password"); } + String jwtToken = (String) context.get("login.token"); + if (jwtToken == null) { + jwtToken = (String) context.get("token"); + } // get the visitId for the history entity String visitId = (String) context.get("visitId"); @@ -119,7 +126,7 @@ public class LoginServices { String errMsg = ""; if (UtilValidate.isEmpty(username)) { errMsg = UtilProperties.getMessage(resource,"loginservices.username_missing", locale); - } else if (UtilValidate.isEmpty(password)) { + } else if (UtilValidate.isEmpty(password) && UtilValidate.isEmpty(jwtToken)) { errMsg = UtilProperties.getMessage(resource,"loginservices.password_missing", locale); } else { @@ -223,11 +230,17 @@ public class LoginServices { // in the usage of userLogin service in ICalWorker.java and XmlRpcEventHandler.java. useTomcatSSO = useTomcatSSO && (request!=null); + // resolve the key for decrypt the token and control the validity + boolean jwtTokenValid = SecurityUtil.authenticateUserLoginByJWT(delegator, username, jwtToken); + // if the password.accept.encrypted.and.plain property in security is set to true allow plain or encrypted passwords // if this is a system account don't bother checking the passwords // if externalAuth passed; this is run as well - if ((!authFatalError && externalAuth) || (useTomcatSSO ? TomcatSSOLogin(request, username, password) : checkPassword(userLogin.getString("currentPassword"), useEncryption, password) )) { - Debug.logVerbose("[LoginServices.userLogin] : Password Matched", module); + if ((!authFatalError && externalAuth) + || (useTomcatSSO && TomcatSSOLogin(request, username, password)) + || (jwtToken != null && jwtTokenValid) + || (password != null && checkPassword(userLogin.getString("currentPassword"), useEncryption, password))) { + Debug.logVerbose("[LoginServices.userLogin] : Password Matched or Token Validated", module); // update the hasLoggedOut flag if (hasLoggedOut == null || hasLoggedOut) { @@ -269,7 +282,8 @@ public class LoginServices { Debug.logInfo("[LoginServices.userLogin] : Password Incorrect", module); // password invalid... - errMsg = UtilProperties.getMessage(resource,"loginservices.password_incorrect", locale); + if (password != null) errMsg = UtilProperties.getMessage(resource,"loginservices.password_incorrect", locale); + else if (jwtToken != null) errMsg = UtilProperties.getMessage(resource,"loginservices.token_incorrect", locale); // increment failed login count Long currentFailedLogins = userLogin.getLong("successiveFailedLogins"); @@ -745,6 +759,16 @@ public class LoginServices { userLoginId = loggedInUserLogin.getString("userLoginId"); } + GenericValue userLoginToUpdate; + + try { + userLoginToUpdate = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); + } catch (GenericEntityException e) { + Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); + errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_read_failure", messageMap, locale); + return ServiceUtil.returnError(errMsg); + } + // <b>security check</b>: userLogin userLoginId must equal userLoginId, or must have PARTYMGR_UPDATE permission // NOTE: must check permission first so that admin users can set own password without specifying old password // TODO: change this security group because we can't use permission groups defined in the applications from the framework. @@ -753,6 +777,9 @@ public class LoginServices { errMsg = UtilProperties.getMessage(resource,"loginservices.not_have_permission_update_password_for_user_login", locale); return ServiceUtil.returnError(errMsg); } + if (UtilValidate.isNotEmpty(context.get("login.token"))) { + adminUser = SecurityUtil.authenticateUserLoginByJWT(delegator, userLoginId, (String) context.get("login.token")); + } } else { adminUser = true; } @@ -762,16 +789,6 @@ public class LoginServices { String newPasswordVerify = (String) context.get("newPasswordVerify"); String passwordHint = (String) context.get("passwordHint"); - GenericValue userLoginToUpdate = null; - - try { - userLoginToUpdate = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); - } catch (GenericEntityException e) { - Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); - errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_read_failure", messageMap, locale); - return ServiceUtil.returnError(errMsg); - } - if (userLoginToUpdate == null) { // this may be a full external authenticator; first try authenticating boolean authenticated = false; Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/common/webcommon/WEB-INF/common-controller.xml URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/common/webcommon/WEB-INF/common-controller.xml?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/common/webcommon/WEB-INF/common-controller.xml (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/common/webcommon/WEB-INF/common-controller.xml Sat Sep 21 19:12:23 2019 @@ -85,23 +85,16 @@ under the License. <event type="java" path="org.apache.ofbiz.securityext.login.LoginEvents" invoke="forgotPassword"/> <response name="success" type="view" value="forgotPassword"/> <response name="error" type="view" value="forgotPassword"/> + <response name="auth" type="request-redirect" value="main" /> </request-map> - <request-map uri="forgotPassword_step1"> - <security https="true" auth="false"/> - <response name="success" type="view" value="forgotPassword_step1"/> - </request-map> - <request-map uri="forgotPassword_step2"> - <security auth="false" https="true"></security> - <response name="success" type="view" value="forgotPassword_step2" /> + <request-map uri="forgotPasswordReset"> + <security https="true" auth="false" /> + <event type="java" path="org.apache.ofbiz.securityext.login.LoginEvents" invoke="changePasswordRequest"/> + <response name="success" type="request-redirect" value="main" /> + <response name="error" type="view" value="forgotPassword" /> </request-map> - <request-map uri="forgotPassword_step3"> - <security https="true" auth="false"/> - <event type="java" path="org.apache.ofbiz.securityext.login.LoginEvents" invoke="forgotPassword"/> - <response name="success" type="view" value="login"/> - <response name="error" type="view" value="forgotPassword_step2"/> - </request-map> <request-map uri="passwordChange"> - <security https="true" auth="false"/> + <security https="true" auth="true"/> <response name="success" type="view" value="requirePasswordChange"/> </request-map> <request-map uri="view"> @@ -360,6 +353,6 @@ under the License. <view-map name="LookupGeo" type="screen" page="component://common/widget/LookupScreens.xml#LookupGeo"/> <view-map name="LookupGeoName" type="screen" page="component://common/widget/LookupScreens.xml#LookupGeoName"/> <view-map name="LookupLocale" type="screen" page="component://common/widget/LookupScreens.xml#LookupLocale"/> - <view-map name="forgotPassword_step1" type="screen" page="component://common/widget/CommonScreens.xml#forgotPassword_step1"/> - <view-map name="forgotPassword_step2" type="screen" page="component://common/widget/CommonScreens.xml#forgotPassword_step2"/> + <view-map name="forgotPassword" type="screen" page="component://common/widget/CommonScreens.xml#forgotPassword"/> + </site-conf> Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/common/widget/CommonScreens.xml URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/common/widget/CommonScreens.xml?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/common/widget/CommonScreens.xml (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/common/widget/CommonScreens.xml Sat Sep 21 19:12:23 2019 @@ -269,34 +269,6 @@ under the License. </section> </screen> - <screen name="forgotPassword_step1"> - <section> - <widgets> - <include-screen name="MinimalActions" /> - <include-screen name="forgotPassword_step1" location="${groovy:commonScreenLocations.forgotPassword_step1?commonScreenLocations.forgotPassword_step1:commonDecoratorLocation}"/> - </widgets> - </section> - </screen> - - <screen name="forgotPassword_step2"> - <section> - <actions> - <set field="userLoginId" from-field="parameters.USERNAME"/> - <entity-and entity-name="UserLoginSecurityQuestion" list="securityQuestions"> - <field-map field-name="userLoginId" /> - </entity-and> - <set field="questionEnumId" from-field="securityQuestions[0].questionEnumId" /> - <entity-one entity-name="Enumeration" value-field="securityQuestion"> - <field-map field-name="enumId" from-field="questionEnumId"/> - </entity-one> - </actions> - <widgets> - <include-screen name="MinimalActions" /> - <include-screen name="forgotPassword_step2" location="${groovy:commonScreenLocations.forgotPassword_step2?commonScreenLocations.forgotPassword_step2:commonDecoratorLocation}"/> - </widgets> - </section> - </screen> - <screen name="forgotPassword"> <section> <widgets> Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/security/config/security.properties URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/security/config/security.properties?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/security/config/security.properties (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/security/config/security.properties Sat Sep 21 19:12:23 2019 @@ -83,9 +83,6 @@ security.login.authorised.during.imperso # -- should we encrypt (SHA Hash) the password? -- password.encrypt=true -# -- set requirePasswordChange to true, after emailPassword -- -password.email_password.require_password_change=true - # -- specify the type of hash to use for one-way encryption, will be passed to java.security.MessageDigest.getInstance() -- # -- options may include: SHA, PBKDF2WithHmacSHA1, PBKDF2WithHmacSHA256, PBKDF2WithHmacSHA384, PBKDF2WithHmacSHA512 and etc password.encrypt.hash.type=SHA @@ -98,6 +95,9 @@ password.encrypt.pbkdf2.iterations=10000 # -- SHOULD GENERALLY NOT BE TRUE FOR PRODUCTION SITES, but is useful for interim periods when going to password encryption -- password.accept.encrypted.and.plain=false +# -- set request life time after a password change (like email) in hours, set -1 if you want disable it -- +password.request.change.timeout=24 + # -- should we convert usernames and passwords to lowercase? (useful for case insensitive usernames and passwords) -- username.lowercase=false password.lowercase=false @@ -139,14 +139,15 @@ default.error.response.view=view:viewBlo security.login.externalLoginKey.enabled=true # -- Security key used to encrypt and decrypt the autogenerated password in forgot password functionality. -login.secret_key_string=Secret Key +# Read Passwords and JWT (JSON Web Tokens) usage documentation to choose the way you want to store this key +login.secret_key_string=login.secret_key_string # -- Time To Live of the token send to the external server in seconds, 10 seconds seems plenty enough OOTB. Custom projects might want set a lower value. -security.jwt.token.expireTime=10 +security.jwt.token.expireTime=1800 # -- Enables the internal Single Sign On feature which allows a token based login between OFBiz instances # -- To make this work you also have to configure a secret key with security.token.key security.internal.sso.enabled=false -# -- The secret key for the JWT token signature. Configuration in the SystemProperty entity is recommended for security reasons. -#security.token.key= +# -- The secret key for the JWT token signature. Read Passwords and JWT (JSON Web Tokens) usage documentation to choose the way you want to store this key +security.token.key=security.token.key Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/security/src/main/java/org/apache/ofbiz/security/SecurityUtil.java Sat Sep 21 19:12:23 2019 @@ -21,6 +21,7 @@ package org.apache.ofbiz.security; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.StringUtil; @@ -28,10 +29,13 @@ import org.apache.ofbiz.base.util.UtilMi import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; +import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.condition.EntityCondition; import org.apache.ofbiz.entity.condition.EntityOperator; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.entity.util.EntityUtil; +import org.apache.ofbiz.service.ServiceUtil; +import org.apache.ofbiz.webapp.control.JWTManager; /** * A <code>Security</code> util. @@ -118,4 +122,32 @@ public final class SecurityUtil { && !adminPermissions.contains(perm.substring(0, perm.lastIndexOf("_")))) .collect(Collectors.toList()); } + + /** + * Return a JWToken for authenticate a userLogin with salt the token by userLoginId and currentPassword + */ + public static String generateJwtToAuthenticateUserLogin(Delegator delegator, String userLoginId) + throws GenericEntityException { + GenericValue userLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); + Map<String, String> claims = UtilMisc.toMap("userLoginId", userLogin.getString("userLoginId")); + return JWTManager.createJwt(delegator, claims, + userLogin.getString("userLoginId") + userLogin.getString("currentPassword"), - 1); + } + + /** + * For a jwtToken and userLoginId check the coherence between them + */ + public static boolean authenticateUserLoginByJWT(Delegator delegator, String userLoginId, String jwtToken) { + if (UtilValidate.isNotEmpty(jwtToken)) { + try { + GenericValue userLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); + Map<String, Object> claims = JWTManager.validateToken(delegator, jwtToken, + userLogin.getString("userLoginId") + userLogin.getString("currentPassword")); + return (! ServiceUtil.isError(claims)) && userLoginId.equals(claims.get("userLoginId")); + } catch (GenericEntityException e) { + Debug.logWarning("failed to validate a jwToken for user " + userLoginId, module); + } + } + return false; + } } \ No newline at end of file Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/service/servicedef/services.xml URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/service/servicedef/services.xml?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/service/servicedef/services.xml (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/service/servicedef/services.xml Sat Sep 21 19:12:23 2019 @@ -90,7 +90,8 @@ under the License. <service name="authenticationInterface" engine="interface"> <description>Interface to describe authentication services</description> <attribute name="login.username" type="String" mode="IN"/> - <attribute name="login.password" type="String" mode="IN"/> + <attribute name="login.password" type="String" mode="IN" optional="true"/> + <attribute name="login.token" type="String" mode="IN" optional="true"/> <attribute name="visitId" type="String" mode="IN" optional="true"/> <attribute name="isServiceAuth" type="Boolean" mode="IN" optional="true"/> <attribute name="userLogin" type="org.apache.ofbiz.entity.GenericValue" mode="OUT"/> Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/service/src/main/java/org/apache/ofbiz/service/ServiceDispatcher.java URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/service/src/main/java/org/apache/ofbiz/service/ServiceDispatcher.java?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/service/src/main/java/org/apache/ofbiz/service/ServiceDispatcher.java (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/service/src/main/java/org/apache/ofbiz/service/ServiceDispatcher.java Sat Sep 21 19:12:23 2019 @@ -908,14 +908,15 @@ public class ServiceDispatcher { if (UtilValidate.isNotEmpty(context.get("login.username"))) { // check for a username/password, if there log the user in and make the userLogin object String username = (String) context.get("login.username"); + Locale locale = (Locale) context.get("locale"); if (UtilValidate.isNotEmpty(context.get("login.password"))) { String password = (String) context.get("login.password"); - context.put("userLogin", getLoginObject(service, localName, username, password, (Locale) context.get("locale"))); + context.put("userLogin", getLoginObject(service, localName, username, password, null, locale)); context.remove("login.password"); } else { - context.put("userLogin", getLoginObject(service, localName, username, null, (Locale) context.get("locale"))); + context.put("userLogin", getLoginObject(service, localName, username, null, (String) context.get("login.token"), locale)); } context.remove("login.username"); } else { @@ -977,8 +978,8 @@ public class ServiceDispatcher { } // gets a value object from name/password pair - private GenericValue getLoginObject(String service, String localName, String username, String password, Locale locale) throws GenericServiceException { - Map<String, Object> context = UtilMisc.toMap("login.username", username, "login.password", password, "isServiceAuth", true, "locale", locale); + private GenericValue getLoginObject(String service, String localName, String username, String password, String jwtToken, Locale locale) throws GenericServiceException { + Map<String, Object> context = UtilMisc.toMap("login.username", username, "login.password", password, "login.token", jwtToken, "isServiceAuth", true, "locale", locale); if (Debug.verboseOn()) Debug.logVerbose("[ServiceDispathcer.authenticate] : Invoking UserLogin Service", module); Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java Sat Sep 21 19:12:23 2019 @@ -18,8 +18,22 @@ */ package org.apache.ofbiz.webapp.control; -import io.jsonwebtoken.*; -import org.apache.ofbiz.base.util.*; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.HttpHeaders; + +import org.apache.ofbiz.base.util.Debug; +import org.apache.ofbiz.base.util.StringUtil; +import org.apache.ofbiz.base.util.UtilDateTime; +import org.apache.ofbiz.base.util.UtilHttp; +import org.apache.ofbiz.base.util.UtilMisc; +import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.DelegatorFactory; import org.apache.ofbiz.entity.GenericEntityException; @@ -32,13 +46,13 @@ import org.apache.ofbiz.service.ModelSer import org.apache.ofbiz.service.ServiceUtil; import org.apache.ofbiz.webapp.WebAppUtil; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.HttpHeaders; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; /** * This class manages the single sign-on authentication through JWT tokens between OFBiz applications. @@ -120,7 +134,21 @@ public class JWTManager { * @return the JWT secret key */ public static String getJWTKey(Delegator delegator) { - return EntityUtilProperties.getPropertyValue("security", "security.token.key", delegator); + return getJWTKey(delegator, null); + } + + /** + * Get the JWT secret key from database or security.properties. + * @param delegator the delegator + * @return the JWT secret key + */ + + public static String getJWTKey(Delegator delegator, String salt) { + String key = EntityUtilProperties.getPropertyValue("security", "security.token.key", delegator); + if (salt != null) { + return StringUtil.toHexString(salt.getBytes()) + key; + } + return key; } /** @@ -197,37 +225,54 @@ public class JWTManager { return headerAuthValue.replaceFirst(bearerPrefix, "").trim(); } - /* Validates the provided token using the secret key. + /** Validates the provided token using the secret key. * If the token is valid it will get the conteined claims and return them. * If token validation failed it will return an error. * Public for API access from third party code. * - * @param token the JWT token + * @param jwtToken the JWT token * @param key the server side key to verify the signature - * @return Map of the claims contained in the token + * @return Map of the claims contained in the token or an error */ public static Map<String, Object> validateToken(String jwtToken, String key) { Map<String, Object> result = new HashMap<String, Object>(); if (UtilValidate.isEmpty(jwtToken) || UtilValidate.isEmpty(key)) { String msg = "JWT token or key can not be empty."; Debug.logError(msg, module); - result.put(ModelService.ERROR_MESSAGE, msg); - return result; + return ServiceUtil.returnError(msg); } try { - Claims claims = Jwts.parser().setSigningKey(key.getBytes()).parseClaimsJws(jwtToken).getBody(); + JWTVerifier verifToken = JWT.require(Algorithm.HMAC512(key)) + .withIssuer("ApacheOFBiz") + .build(); + DecodedJWT jwt = verifToken.verify(jwtToken); + Map<String, Claim> claims = jwt.getClaims(); //OK, we can trust this JWT - result.putAll(claims); + for (Map.Entry<String, Claim> entry : claims.entrySet()) { + result.put(entry.getKey(), entry.getValue().asString()); + } return result; - } catch (SignatureException | ExpiredJwtException e) { + } catch (JWTVerificationException e) { // signature not valid or token expired Debug.logError(e.getMessage(), module); - result.put(ModelService.ERROR_MESSAGE, e.getMessage()); - return result; + return ServiceUtil.returnError(e.getMessage()); } } /** + * Validates the provided token using a salt to recreate the key from the secret + * If the token is valid it will get the conteined claims and return them. + * If token validation failed it will return an error. + * @param delegator + * @param jwtToken + * @param keySalt + * @return Map of the claims contained in the token or an error + */ + public static Map<String, Object> validateToken(Delegator delegator, String jwtToken, String keySalt) { + return validateToken(jwtToken, JWTManager.getJWTKey(delegator, keySalt)); + } + + /** * Create and return a JWT token using the claims of the provided map and the configured expiration time. * @param delegator the delegator * @param claims the map containing the JWT claims @@ -238,29 +283,46 @@ public class JWTManager { return createJwt(delegator, claims, expirationTime); } - /* Create and return a JWT token using the claims of the provided map and the provided expiration time. + /** Create and return a JWT token using the claims of the provided map and the provided expiration time. * * @param delegator - * @param tokenMap the map containing the JWT claims + * @param claims the map containing the JWT claims * @param expireTime the expiration time in seconds * @return a JWT token */ public static String createJwt(Delegator delegator, Map<String, String> claims, int expireTime) { - String key = JWTManager.getJWTKey(delegator); + return createJwt(delegator, claims, null, expireTime); + } + + /** Create and return a JWT token using the claims of the provided map and the provided expiration time. + * + * @param delegator + * @param claims the map containing the JWT claims + * @param keySalt salt to use as prefix on the encrypt key + * @param expireTime the expiration time in seconds + * @return a JWT token + */ + public static String createJwt(Delegator delegator, Map<String, String> claims, String keySalt, int expireTime) { + if (expireTime <= 0) { + expireTime = Integer.parseInt(EntityUtilProperties.getPropertyValue("security", "security.jwt.token.expireTime", "1800", delegator)); + } + + String key = JWTManager.getJWTKey(delegator, keySalt); Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(UtilDateTime.nowTimestamp().getTime()); + Timestamp now = UtilDateTime.nowTimestamp(); + cal.setTimeInMillis(now.getTime()); cal.add(Calendar.SECOND, expireTime); - JwtBuilder builder = Jwts.builder() - .setExpiration(cal.getTime()) - .setIssuedAt(UtilDateTime.nowTimestamp()) - .signWith(SignatureAlgorithm.HS512, key.getBytes()); - + JWTCreator.Builder builder = JWT.create() + .withIssuedAt(now) + .withExpiresAt(cal.getTime()) + .withIssuer("ApacheOFBiz"); for (Map.Entry<String, String> entry : claims.entrySet()) { - builder.claim(entry.getKey(), entry.getValue()); + builder.withClaim(entry.getKey(), entry.getValue()); } - return builder.compact(); + + return builder.sign(Algorithm.HMAC512(key)); } /** Modified: ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java (original) +++ ofbiz/ofbiz-framework/branches/release18.12/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/LoginWorker.java Sat Sep 21 19:12:23 2019 @@ -327,17 +327,22 @@ public class LoginWorker { String username = null; String password = null; + String token = null; if (userLogin == null) { // check parameters username = request.getParameter("USERNAME"); password = request.getParameter("PASSWORD"); + token = request.getParameter("TOKEN"); // check session attributes if (username == null) username = (String) session.getAttribute("USERNAME"); if (password == null) password = (String) session.getAttribute("PASSWORD"); + if (token == null) token = (String) session.getAttribute("TOKEN"); // in this condition log them in if not already; if not logged in or can't log in, save parameters and return error - if ((username == null) || (password == null) || ("error".equals(login(request, response)))) { + if (username == null + || (password == null && token == null) + || "error".equals(login(request, response))) { // make sure this attribute is not in the request; this avoids infinite recursion when a login by less stringent criteria (like not checkout the hasLoggedOut field) passes; this is not a normal circumstance but can happen with custom code or in funny error situations when the userLogin service gets the userLogin object but runs into another problem and fails to return an error request.removeAttribute("_LOGIN_PASSED_"); @@ -394,6 +399,7 @@ public class LoginWorker { Delegator delegator = (Delegator) request.getAttribute("delegator"); String username = request.getParameter("USERNAME"); String password = request.getParameter("PASSWORD"); + String token = request.getParameter("TOKEN"); String forgotPwdFlag = request.getParameter("forgotPwdFlag"); // password decryption @@ -415,6 +421,7 @@ public class LoginWorker { if (username == null) username = (String) session.getAttribute("USERNAME"); if (password == null) password = (String) session.getAttribute("PASSWORD"); + if (token == null) token = (String) session.getAttribute("TOKEN"); // allow a username and/or password in a request attribute to override the request parameter or the session attribute; this way a preprocessor can play with these a bit... if (UtilValidate.isNotEmpty(request.getAttribute("USERNAME"))) { @@ -423,12 +430,15 @@ public class LoginWorker { if (UtilValidate.isNotEmpty(request.getAttribute("PASSWORD"))) { password = (String) request.getAttribute("PASSWORD"); } + if (UtilValidate.isNotEmpty(request.getAttribute("TOKEN"))) { + token = (String) request.getAttribute("TOKEN"); + } List<String> unpwErrMsgList = new LinkedList<String>(); if (UtilValidate.isEmpty(username)) { unpwErrMsgList.add(UtilProperties.getMessage(resourceWebapp, "loginevents.username_was_empty_reenter", UtilHttp.getLocale(request))); } - if (UtilValidate.isEmpty(password)) { + if (UtilValidate.isEmpty(password) && UtilValidate.isEmpty(token)) { unpwErrMsgList.add(UtilProperties.getMessage(resourceWebapp, "loginevents.password_was_empty_reenter", UtilHttp.getLocale(request))); } boolean requirePasswordChange = "Y".equals(request.getParameter("requirePasswordChange")); @@ -500,7 +510,13 @@ public class LoginWorker { try { // get the visit id to pass to the userLogin for history String visitId = VisitHandler.getVisitId(session); - result = dispatcher.runSync("userLogin", UtilMisc.toMap("login.username", username, "login.password", password, "visitId", visitId, "locale", UtilHttp.getLocale(request), "request", request)); + result = dispatcher.runSync("userLogin", UtilMisc.toMap( + "login.username", username, + "login.password", password, + "login.token", token, + "visitId", visitId, + "locale", UtilHttp.getLocale(request), + "request", request)); } catch (GenericServiceException e) { Debug.logError(e, "Error calling userLogin service", module); Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); @@ -513,11 +529,14 @@ public class LoginWorker { GenericValue userLogin = (GenericValue) result.get("userLogin"); if (requirePasswordChange) { - Map<String, Object> inMap = UtilMisc.<String, Object>toMap("login.username", username, "login.password", password, "locale", UtilHttp.getLocale(request)); - inMap.put("userLoginId", username); - inMap.put("currentPassword", password); - inMap.put("newPassword", request.getParameter("newPassword")); - inMap.put("newPasswordVerify", request.getParameter("newPasswordVerify")); + Map<String, Object> inMap = UtilMisc.<String, Object>toMap( + "login.username", username, + "login.password", password, + "login.token", token, + "userLoginId", username, + "currentPassword", password, + "newPassword", request.getParameter("newPassword"), + "newPasswordVerify", request.getParameter("newPasswordVerify")); Map<String, Object> resultPasswordChange = null; try { resultPasswordChange = dispatcher.runSync("updatePassword", inMap); Modified: ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ChangePassword.ftl URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ChangePassword.ftl?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ChangePassword.ftl (original) +++ ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ChangePassword.ftl Sat Sep 21 19:12:23 2019 @@ -31,6 +31,7 @@ under the License. <form method="post" action="<@ofbizUrl>login</@ofbizUrl>" name="loginform"> <input type="hidden" name="requirePasswordChange" value="Y"/> <input type="hidden" name="USERNAME" value="${username}"/> + <input type="hidden" name="TOKEN" value="${parameters.TOKEN!}"/> <input type="hidden" name="userTenantId" value="${tenantId}"/> <input type="hidden" name="forgotPwdFlag" value="${parameters.forgotPwdFlag!}" /> <table cellspacing="0"> Modified: ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ForgotPassword.ftl URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ForgotPassword.ftl?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ForgotPassword.ftl (original) +++ ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/ForgotPassword.ftl Sat Sep 21 19:12:23 2019 @@ -16,32 +16,79 @@ KIND, either express or implied. See th specific language governing permissions and limitations under the License. --> - +<#if securityQuestion??> + <#assign messageTitle = uiLabelMap.AnswerSecurityQuestion> +<#else> + <#assign messageTitle = uiLabelMap.CommonForgotYourPassword> +</#if> +<#if ! userLoginId??> + <#assign userLoginId = requestParameters.USERNAME!> + <#if ! userLoginId?? && autoUserLogin??> + <#assign userLoginId = autoUserLogin.userLoginId> + </#if> +</#if> <center> - <div class="screenlet login-screenlet"> - <div class="screenlet-title-bar"> - <h3>${uiLabelMap.CommonForgotYourPassword}?</h3> + <div class="screenlet login-screenlet"> + <div class="screenlet-title-bar"> + <h3>${messageTitle}</h3> + </div> + <div class="screenlet-body"> + <form method="post" action="<@ofbizUrl>${forgotPasswordTarget?default("forgotPassword")}</@ofbizUrl>" name="forgotpassword"> + <table class="basic-table" cellspacing="0"> + <tr> + <td class="label">${uiLabelMap.CommonUsername}</td> + <td><input type="text" size="20" name="USERNAME" value="${userLoginId!}"/></td> + </tr> + <#if securityQuestion?has_content> + <tr> + <td class="label">${uiLabelMap.SecurityQuestion}</td> + <td> + ${securityQuestion.description!} + <input type="hidden" name="securityQuestion" value="${securityQuestion.enumId!}" /> + </td> + </tr> + <tr> + <td class="label">${uiLabelMap.SecurityAnswer}</td> + <td> + <input type="text" name="securityAnswer" class="" value="" /> + </td> + </tr> + <tr> + <td colspan="2" align="center"> + <input type="submit" name="GET_PASSWORD_HINT" class="smallSubmit" value="${uiLabelMap.CommonGetPasswordHint}"/> + </td> + </tr> + <#elseif requestParameters.token??> + <input type="hidden" name="token" value="${requestParameters.token}"/> + <tr> + <td class="label">${uiLabelMap.CommonNewPassword}</td> + <td><input type="password" name="newPassword" autocomplete="off" value="" size="20"/></td> + </tr> + <tr> + <td class="label">${uiLabelMap.CommonNewPasswordVerify}</td> + <td><input type="password" name="newPasswordVerify" autocomplete="off" value="" size="20"/></td> + </tr> + <tr> + <td colspan="2" align="center"> + <input type="submit" class="smallSubmit" value="${uiLabelMap.CommonContinue}"/> + </td> + </tr> + <#else> + <tr> + <td colspan="2" align="center"> + <input type="submit" name="GET_PASSWORD_HINT" class="smallSubmit" value="${uiLabelMap.CommonGetPasswordHint}" /> + <input type="submit" name="EMAIL_PASSWORD" class="smallSubmit" value="${uiLabelMap.CommonEmailPassword}" /> + </td> + </tr> + <tr> + <td colspan="2" align="center"> + <a href='#' class="buttontext" onclick="window.history.back();">${uiLabelMap.CommonGoBack}</a> + </td> + </tr> + </#if> + </table> + <input type="hidden" name="JavaScriptEnabled" value="N" /> + </form> + </div> </div> - <div class="screenlet-body"> - <form method="post" action="<@ofbizUrl>forgotPassword_step2</@ofbizUrl>" name="getSecurityQuestion"> - <table class="basic-table" cellspacing="0"> - <tr> - <td class="label">${uiLabelMap.CommonUsername}</td> - <td><input type="text" size="20" name="USERNAME" value="<#if requestParameters.USERNAME?has_content>${requestParameters.USERNAME}<#elseif autoUserLogin?has_content>${autoUserLogin.userLoginId}</#if>"/></td> - </tr> - <tr> - <td colspan="2" align="center"> - <input type="submit" class="smallSubmit" value="${uiLabelMap.CommonContinue}"/> - </td> - </tr> - <tr> - <td colspan="2" align="center"> - <a href='<@ofbizUrl>authview</@ofbizUrl>' class="buttontext">${uiLabelMap.CommonGoBack}</a> - </td> - </tr> - </table> - <input type="hidden" name="JavaScriptEnabled" value="N"/> - </form> - </div> - </div> </center> Modified: ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/Login.ftl URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/Login.ftl?rev=1867296&r1=1867295&r2=1867296&view=diff ============================================================================== --- ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/Login.ftl (original) +++ ofbiz/ofbiz-framework/branches/release18.12/themes/common-theme/template/Login.ftl Sat Sep 21 19:12:23 2019 @@ -60,7 +60,7 @@ under the License. </table> <input type="hidden" name="JavaScriptEnabled" value="N"/> <br /> - <a href="<@ofbizUrl>forgotPassword_step1</@ofbizUrl>">${uiLabelMap.CommonForgotYourPassword}?</a> + <a href="<@ofbizUrl>forgotPassword</@ofbizUrl>">${uiLabelMap.CommonForgotYourPassword}?</a> </form> </div> </div> |
Free forum by Nabble | Edit this page |