| /******************************************************************************* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| *******************************************************************************/ |
| |
| package org.apache.ofbiz.common.login; |
| |
| import java.sql.Timestamp; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.transaction.Transaction; |
| |
| import org.apache.ofbiz.base.crypto.HashCrypt; |
| import org.apache.ofbiz.base.util.Debug; |
| import org.apache.ofbiz.base.util.UtilDateTime; |
| 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.common.authentication.AuthHelper; |
| import org.apache.ofbiz.common.authentication.api.AuthenticatorException; |
| 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.EntityFunction; |
| import org.apache.ofbiz.entity.condition.EntityOperator; |
| import org.apache.ofbiz.entity.model.ModelEntity; |
| import org.apache.ofbiz.entity.transaction.GenericTransactionException; |
| import org.apache.ofbiz.entity.transaction.TransactionUtil; |
| import org.apache.ofbiz.entity.util.EntityListIterator; |
| import org.apache.ofbiz.entity.util.EntityQuery; |
| import org.apache.ofbiz.entity.util.EntityUtilProperties; |
| import org.apache.ofbiz.security.Security; |
| import org.apache.ofbiz.service.DispatchContext; |
| import org.apache.ofbiz.service.GenericServiceException; |
| import org.apache.ofbiz.service.LocalDispatcher; |
| import org.apache.ofbiz.service.ModelService; |
| import org.apache.ofbiz.service.ServiceUtil; |
| import org.apache.ofbiz.webapp.control.LoginWorker; |
| |
| /** |
| * <b>Title:</b> Login Services |
| */ |
| public class LoginServices { |
| |
| public static final String module = LoginServices.class.getName(); |
| public static final String resource = "SecurityextUiLabels"; |
| |
| /** Login service to authenticate username and password |
| * @return Map of results including (userLogin) GenericValue object |
| */ |
| public static Map<String, Object> userLogin(DispatchContext ctx, Map<String, ?> context) { |
| LocalDispatcher dispatcher = ctx.getDispatcher(); |
| Locale locale = (Locale) context.get("locale"); |
| Delegator delegator = ctx.getDelegator(); |
| |
| // load the external auth modules -- note: this will only run once and cache the objects |
| if (!AuthHelper.authenticatorsLoaded()) { |
| AuthHelper.loadAuthenticators(dispatcher); |
| } |
| |
| // Authenticate to LDAP if configured to do so |
| // TODO: this should be moved to using the NEW Authenticator API |
| if ("true".equals(EntityUtilProperties.getPropertyValue("security", "security.ldap.enable", delegator))) { |
| if (!LdapAuthenticationServices.userLogin(ctx, context)) { |
| String errMsg = UtilProperties.getMessage(resource, "loginservices.ldap_authentication_failed", locale); |
| if ("true".equals(EntityUtilProperties.getPropertyValue("security", "security.ldap.fail.login", delegator))) { |
| return ServiceUtil.returnError(errMsg); |
| } else { |
| Debug.logInfo(errMsg, module); |
| } |
| } |
| } |
| |
| Map<String, Object> result = new LinkedHashMap<String, Object>(); |
| boolean useEncryption = "true".equals(EntityUtilProperties.getPropertyValue("security", "password.encrypt", delegator)); |
| |
| // if isServiceAuth is not specified, default to not a service auth |
| boolean isServiceAuth = context.get("isServiceAuth") != null && ((Boolean) context.get("isServiceAuth")).booleanValue(); |
| |
| String username = (String) context.get("login.username"); |
| if (username == null) username = (String) context.get("username"); |
| String password = (String) context.get("login.password"); |
| if (password == null) password = (String) context.get("password"); |
| |
| // get the visitId for the history entity |
| String visitId = (String) context.get("visitId"); |
| |
| String errMsg = ""; |
| if (UtilValidate.isEmpty(username)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.username_missing", locale); |
| } else if (UtilValidate.isEmpty(password)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_missing", locale); |
| } else { |
| |
| if ("true".equalsIgnoreCase(EntityUtilProperties.getPropertyValue("security", "username.lowercase", delegator))) { |
| username = username.toLowerCase(); |
| } |
| if ("true".equalsIgnoreCase(EntityUtilProperties.getPropertyValue("security", "password.lowercase", delegator))) { |
| password = password.toLowerCase(); |
| } |
| |
| boolean repeat = true; |
| // starts at zero but it incremented at the beginning so in the first pass passNumber will be 1 |
| int passNumber = 0; |
| |
| while (repeat) { |
| repeat = false; |
| // pass number is incremented here because there are continues in this loop so it may never get to the end |
| passNumber++; |
| |
| GenericValue userLogin = null; |
| |
| try { |
| // only get userLogin from cache for service calls; for web and other manual logins there is less time sensitivity |
| userLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", username).cache(isServiceAuth).queryOne(); |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| } |
| |
| |
| // see if any external auth modules want to sync the user info |
| if (userLogin == null) { |
| try { |
| AuthHelper.syncUser(username); |
| } catch (AuthenticatorException e) { |
| Debug.logWarning(e, module); |
| } |
| |
| // check the user login object again |
| try { |
| userLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", username).cache(isServiceAuth).queryOne(); |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| } |
| } |
| |
| if (userLogin != null) { |
| String ldmStr = EntityUtilProperties.getPropertyValue("security", "login.disable.minutes", delegator); |
| long loginDisableMinutes = 30; |
| |
| try { |
| loginDisableMinutes = Long.parseLong(ldmStr); |
| } catch (Exception e) { |
| loginDisableMinutes = 30; |
| Debug.logWarning("Could not parse login.disable.minutes from security.properties, using default of 30", module); |
| } |
| |
| Timestamp disabledDateTime = userLogin.getTimestamp("disabledDateTime"); |
| Timestamp reEnableTime = null; |
| |
| if (loginDisableMinutes > 0 && disabledDateTime != null) { |
| reEnableTime = new Timestamp(disabledDateTime.getTime() + loginDisableMinutes * 60000); |
| } |
| |
| boolean doStore = true; |
| // we might change & store this userLogin, so we should clone it here to get a mutable copy |
| userLogin = GenericValue.create(userLogin); |
| |
| // get the is system flag -- system accounts can only be used for service authentication |
| boolean isSystem = (isServiceAuth && userLogin.get("isSystem") != null) ? |
| "Y".equalsIgnoreCase(userLogin.getString("isSystem")) : false; |
| |
| // grab the hasLoggedOut flag |
| boolean hasLoggedOut = userLogin.get("hasLoggedOut") != null ? |
| "Y".equalsIgnoreCase(userLogin.getString("hasLoggedOut")) : false; |
| |
| if ((UtilValidate.isEmpty(userLogin.getString("enabled")) || "Y".equals(userLogin.getString("enabled")) || |
| (reEnableTime != null && reEnableTime.before(UtilDateTime.nowTimestamp())) || (isSystem)) && UtilValidate.isEmpty(userLogin.getString("disabledBy"))) { |
| |
| String successfulLogin; |
| |
| if (!isSystem) { |
| userLogin.set("enabled", "Y"); |
| userLogin.set("disabledBy", null); |
| } |
| |
| // attempt to authenticate with Authenticator class(es) |
| boolean authFatalError = false; |
| boolean externalAuth = false; |
| try { |
| externalAuth = AuthHelper.authenticate(username, password, isServiceAuth); |
| } catch (AuthenticatorException e) { |
| // fatal error -- or single authenticator found -- fail now |
| Debug.logWarning(e, module); |
| authFatalError = true; |
| |
| } |
| // 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) || checkPassword(userLogin.getString("currentPassword"), useEncryption, password)) { |
| Debug.logVerbose("[LoginServices.userLogin] : Password Matched", module); |
| |
| // update the hasLoggedOut flag |
| if (hasLoggedOut) { |
| userLogin.set("hasLoggedOut", "N"); |
| } |
| |
| // reset failed login count if necessry |
| Long currentFailedLogins = userLogin.getLong("successiveFailedLogins"); |
| if (currentFailedLogins != null && currentFailedLogins.longValue() > 0) { |
| userLogin.set("successiveFailedLogins", Long.valueOf(0)); |
| } else if (!hasLoggedOut) { |
| // successful login & no loggout flag, no need to change anything, so don't do the store |
| doStore = false; |
| } |
| |
| successfulLogin = "Y"; |
| |
| if (!isServiceAuth) { |
| // get the UserLoginSession if this is not a service auth |
| Map<?, ?> userLoginSessionMap = LoginWorker.getUserLoginSession(userLogin); |
| |
| // return the UserLoginSession Map |
| if (userLoginSessionMap != null) { |
| result.put("userLoginSession", userLoginSessionMap); |
| } |
| } |
| |
| result.put("userLogin", userLogin); |
| result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS); |
| } else { |
| // password is incorrect, but this may be the result of a stale cache entry, |
| // so lets clear the cache and try again if this is the first pass |
| // but only if authFatalError is not true; this would mean the single authenticator failed |
| if (!authFatalError && isServiceAuth && passNumber <= 1) { |
| delegator.clearCacheLine("UserLogin", UtilMisc.toMap("userLoginId", username)); |
| repeat = true; |
| continue; |
| } |
| |
| Debug.logInfo("[LoginServices.userLogin] : Password Incorrect", module); |
| // password invalid... |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_incorrect", locale); |
| |
| // increment failed login count |
| Long currentFailedLogins = userLogin.getLong("successiveFailedLogins"); |
| |
| if (currentFailedLogins == null) { |
| currentFailedLogins = Long.valueOf(1); |
| } else { |
| currentFailedLogins = Long.valueOf(currentFailedLogins.longValue() + 1); |
| } |
| userLogin.set("successiveFailedLogins", currentFailedLogins); |
| |
| // if failed logins over amount in properties file, disable account |
| String mflStr = EntityUtilProperties.getPropertyValue("security", "max.failed.logins", delegator); |
| long maxFailedLogins = 3; |
| try { |
| maxFailedLogins = Long.parseLong(mflStr); |
| } catch (Exception e) { |
| maxFailedLogins = 3; |
| Debug.logWarning("Could not parse max.failed.logins from security.properties, using default of 3", module); |
| } |
| |
| if (maxFailedLogins > 0 && currentFailedLogins.longValue() >= maxFailedLogins) { |
| userLogin.set("enabled", "N"); |
| userLogin.set("disabledDateTime", UtilDateTime.nowTimestamp()); |
| } |
| |
| successfulLogin = "N"; |
| } |
| |
| // this section is being done in its own transaction rather than in the |
| //current/existing transaction because we may return error and we don't |
| //want that to stop this from getting stored |
| Transaction parentTx = null; |
| boolean beganTransaction = false; |
| |
| try { |
| try { |
| parentTx = TransactionUtil.suspend(); |
| } catch (GenericTransactionException e) { |
| Debug.logError(e, "Could not suspend transaction: " + e.getMessage(), module); |
| } |
| |
| try { |
| beganTransaction = TransactionUtil.begin(); |
| |
| if (doStore) { |
| userLogin.store(); |
| } |
| |
| if ("true".equals(EntityUtilProperties.getPropertyValue("security", "store.login.history", delegator))) { |
| boolean createHistory = true; |
| |
| // only save info on service auth if option set to true to do so |
| if (isServiceAuth && !"true".equals(EntityUtilProperties.getPropertyValue("security", "store.login.history.on.service.auth", delegator))) { |
| createHistory = false; |
| } |
| |
| if (createHistory) { |
| Map<String, Object> ulhCreateMap = UtilMisc.toMap("userLoginId", username, "visitId", visitId, |
| "fromDate", UtilDateTime.nowTimestamp(), "successfulLogin", successfulLogin); |
| |
| ModelEntity modelUserLogin = userLogin.getModelEntity(); |
| if (modelUserLogin.isField("partyId")) { |
| ulhCreateMap.put("partyId", userLogin.get("partyId")); |
| } |
| |
| // ONLY save the password if it was incorrect |
| if ("N".equals(successfulLogin) && !"false".equals(EntityUtilProperties.getPropertyValue("security", "store.login.history.incorrect.password", delegator))) { |
| ulhCreateMap.put("passwordUsed", password); |
| } |
| |
| delegator.create("UserLoginHistory", ulhCreateMap); |
| } |
| } |
| } catch (GenericEntityException e) { |
| String geeErrMsg = "Error saving UserLoginHistory"; |
| if (doStore) { |
| geeErrMsg += " and updating login status to reset hasLoggedOut, unsuccessful login count, etc."; |
| } |
| geeErrMsg += ": " + e.toString(); |
| try { |
| TransactionUtil.rollback(beganTransaction, geeErrMsg, e); |
| } catch (GenericTransactionException e2) { |
| Debug.logError(e2, "Could not rollback nested transaction: " + e2.getMessage(), module); |
| } |
| |
| // if doStore is true then this error should not be ignored and we shouldn't consider it a successful login if this happens as there is something very wrong lower down that will bite us again later |
| if (doStore) { |
| return ServiceUtil.returnError(geeErrMsg); |
| } |
| } finally { |
| try { |
| TransactionUtil.commit(beganTransaction); |
| } catch (GenericTransactionException e) { |
| Debug.logError(e, "Could not commit nested transaction: " + e.getMessage(), module); |
| } |
| } |
| } finally { |
| // resume/restore parent transaction |
| if (parentTx != null) { |
| try { |
| TransactionUtil.resume(parentTx); |
| Debug.logVerbose("Resumed the parent transaction.", module); |
| } catch (GenericTransactionException e) { |
| Debug.logError(e, "Could not resume parent nested transaction: " + e.getMessage(), module); |
| } |
| } |
| } |
| } else { |
| // account is disabled, but this may be the result of a stale cache entry, |
| // so lets clear the cache and try again if this is the first pass |
| if (isServiceAuth && passNumber <= 1) { |
| delegator.clearCacheLine("UserLogin", UtilMisc.toMap("userLoginId", username)); |
| repeat = true; |
| continue; |
| } |
| |
| Map<String, Object> messageMap = UtilMisc.<String, Object>toMap("username", username); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.account_for_user_login_id_disabled",messageMap ,locale); |
| if (disabledDateTime != null) { |
| messageMap = UtilMisc.<String, Object>toMap("disabledDateTime", disabledDateTime); |
| errMsg += " " + UtilProperties.getMessage(resource,"loginservices.since_datetime",messageMap ,locale); |
| } else { |
| errMsg += "."; |
| } |
| |
| if (loginDisableMinutes > 0 && reEnableTime != null) { |
| messageMap = UtilMisc.<String, Object>toMap("reEnableTime", reEnableTime); |
| errMsg += " " + UtilProperties.getMessage(resource,"loginservices.will_be_reenabled",messageMap ,locale); |
| } else { |
| errMsg += " " + UtilProperties.getMessage(resource,"loginservices.not_scheduled_to_be_reenabled",locale); |
| } |
| } |
| } else { |
| // no userLogin object; there may be a non-syncing authenticator |
| boolean externalAuth = false; |
| try { |
| externalAuth = AuthHelper.authenticate(username, password, isServiceAuth); |
| } catch (AuthenticatorException e) { |
| errMsg = e.getMessage(); |
| Debug.logError(e, "External Authenticator had fatal exception : " + e.getMessage(), module); |
| } |
| if (externalAuth) { |
| // external auth passed - create a placeholder object for session |
| userLogin = delegator.makeValue("UserLogin"); |
| userLogin.set("userLoginId", username); |
| userLogin.set("enabled", "Y"); |
| userLogin.set("hasLoggedOut", "N"); |
| result.put("userLogin", userLogin); |
| result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS); |
| //TODO: more than this is needed to support 100% external authentication |
| //TODO: party + security information is needed; Userlogin will need to be stored |
| } else { |
| // userLogin record not found, user does not exist |
| errMsg = UtilProperties.getMessage(resource, "loginservices.user_not_found", locale); |
| Debug.logInfo("[LoginServices.userLogin] Invalid User : '" + username + "'; " + errMsg, module); |
| } |
| } |
| } |
| } |
| |
| if (errMsg.length() > 0) { |
| result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_FAIL); |
| result.put(ModelService.ERROR_MESSAGE, errMsg); |
| } |
| return result; |
| } |
| |
| public static void createUserLoginPasswordHistory(Delegator delegator,String userLoginId, String currentPassword) throws GenericEntityException{ |
| int passwordChangeHistoryLimit = 0; |
| try { |
| passwordChangeHistoryLimit = EntityUtilProperties.getPropertyAsInteger("security", "password.change.history.limit", 0).intValue(); |
| } catch (NumberFormatException nfe) { |
| //No valid value is found so don't bother to save any password history |
| passwordChangeHistoryLimit = 0; |
| } |
| if (passwordChangeHistoryLimit == 0 || passwordChangeHistoryLimit < 0) { |
| // Not saving password history, so return from here. |
| return; |
| } |
| |
| EntityListIterator eli = EntityQuery.use(delegator) |
| .from("UserLoginPasswordHistory") |
| .where("userLoginId", userLoginId) |
| .orderBy("-fromDate") |
| .cursorScrollInsensitive() |
| .queryIterator(); |
| Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); |
| GenericValue pwdHist; |
| if ((pwdHist = eli.next()) != null) { |
| // updating password so set end date on previous password in history |
| pwdHist.set("thruDate", nowTimestamp); |
| pwdHist.store(); |
| // check if we have hit the limit on number of password changes to be saved. If we did then delete the oldest password from history. |
| eli.last(); |
| int rowIndex = eli.currentIndex(); |
| if (rowIndex==passwordChangeHistoryLimit) { |
| eli.afterLast(); |
| pwdHist = eli.previous(); |
| pwdHist.remove(); |
| } |
| } |
| eli.close(); |
| |
| // save this password in history |
| GenericValue userLoginPwdHistToCreate = delegator.makeValue("UserLoginPasswordHistory", UtilMisc.toMap("userLoginId", userLoginId,"fromDate", nowTimestamp)); |
| boolean useEncryption = "true".equals(EntityUtilProperties.getPropertyValue("security", "password.encrypt", delegator)); |
| userLoginPwdHistToCreate.set("currentPassword", useEncryption ? HashCrypt.cryptUTF8(getHashType(), null, currentPassword) : currentPassword); |
| userLoginPwdHistToCreate.create(); |
| } |
| |
| /** Creates a UserLogin |
| *@param ctx The DispatchContext that this service is operating in |
| *@param context Map containing the input parameters |
| *@return Map with the result of the service, the output parameters |
| */ |
| public static Map<String, Object> createUserLogin(DispatchContext ctx, Map<String, ?> context) { |
| Map<String, Object> result = new LinkedHashMap<String, Object>(); |
| Delegator delegator = ctx.getDelegator(); |
| LocalDispatcher dispatcher = ctx.getDispatcher(); |
| Security security = ctx.getSecurity(); |
| GenericValue loggedInUserLogin = (GenericValue) context.get("userLogin"); |
| List<String> errorMessageList = new LinkedList<String>(); |
| Locale locale = (Locale) context.get("locale"); |
| |
| boolean useEncryption = "true".equals(EntityUtilProperties.getPropertyValue("security", "password.encrypt", delegator)); |
| |
| String userLoginId = (String) context.get("userLoginId"); |
| String partyId = (String) context.get("partyId"); |
| String currentPassword = (String) context.get("currentPassword"); |
| String currentPasswordVerify = (String) context.get("currentPasswordVerify"); |
| String enabled = (String) context.get("enabled"); |
| String passwordHint = (String) context.get("passwordHint"); |
| String requirePasswordChange = (String) context.get("requirePasswordChange"); |
| String externalAuthId = (String) context.get("externalAuthId"); |
| String errMsg = null; |
| |
| String questionEnumId = (String) context.get("securityQuestion"); |
| String securityAnswer = (String) context.get("securityAnswer"); |
| |
| // security: don't create a user login if the specified partyId (if not empty) already exists |
| // unless the logged in user has permission to do so (same partyId or PARTYMGR_CREATE) |
| if (UtilValidate.isNotEmpty(partyId)) { |
| GenericValue party = null; |
| |
| try { |
| party = EntityQuery.use(delegator).from("Party").where("partyId", partyId).queryOne(); |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| } |
| |
| if (party != null) { |
| if (loggedInUserLogin != null) { |
| // <b>security check</b>: userLogin partyId must equal partyId, or must have PARTYMGR_CREATE permission |
| if (!partyId.equals(loggedInUserLogin.getString("partyId"))) { |
| if (!security.hasEntityPermission("PARTYMGR", "_CREATE", loggedInUserLogin)) { |
| |
| errMsg = UtilProperties.getMessage(resource,"loginservices.party_with_specified_party_ID_exists_not_have_permission", locale); |
| errorMessageList.add(errMsg); |
| } |
| } |
| } else { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.must_be_logged_in_and_permission_create_login_party_ID_exists", locale); |
| errorMessageList.add(errMsg); |
| } |
| } |
| } |
| |
| GenericValue userLoginToCreate = delegator.makeValue("UserLogin", UtilMisc.toMap("userLoginId", userLoginId)); |
| checkNewPassword(userLoginToCreate, null, currentPassword, currentPasswordVerify, passwordHint, errorMessageList, true, locale); |
| userLoginToCreate.set("externalAuthId", externalAuthId); |
| userLoginToCreate.set("passwordHint", passwordHint); |
| userLoginToCreate.set("enabled", enabled); |
| userLoginToCreate.set("requirePasswordChange", requirePasswordChange); |
| userLoginToCreate.set("currentPassword", useEncryption ? HashCrypt.cryptUTF8(getHashType(), null, currentPassword) : currentPassword); |
| try { |
| userLoginToCreate.set("partyId", partyId); |
| } catch (Exception e) { |
| // Will get thrown in framework-only installation |
| Debug.logInfo(e, "Exception thrown while setting UserLogin partyId field: ", module); |
| } |
| |
| try { |
| EntityCondition condition = EntityCondition.makeCondition(EntityFunction.UPPER_FIELD("userLoginId"), EntityOperator.EQUALS, EntityFunction.UPPER(userLoginId)); |
| if (UtilValidate.isNotEmpty(EntityQuery.use(delegator).from("UserLogin").where(condition).queryList())) { |
| Map<String, String> messageMap = UtilMisc.toMap("userLoginId", userLoginId); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_create_login_user_with_ID_exists", messageMap, locale); |
| errorMessageList.add(errMsg); |
| } |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_create_login_user_read_failure", messageMap, locale); |
| errorMessageList.add(errMsg); |
| } |
| |
| if (errorMessageList.size() > 0) { |
| return ServiceUtil.returnError(errorMessageList); |
| } |
| |
| try { |
| userLoginToCreate.create(); |
| createUserLoginPasswordHistory(delegator,userLoginId, currentPassword); |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_create_login_user_write_failure", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| try { |
| if (UtilValidate.isNotEmpty(securityAnswer)) { |
| Map<String, Object> resultMap = new HashMap<String, Object>(); |
| resultMap = dispatcher.runSync("createUserLoginSecurityQuestion", |
| UtilMisc.toMap("userLogin", loggedInUserLogin, "userLoginId", userLoginId, "questionEnumId", questionEnumId, "securityAnswer", securityAnswer)); |
| if (ServiceUtil.isError(resultMap)) { |
| errMsg = ServiceUtil.getErrorMessage(resultMap); |
| errorMessageList.add(errMsg); |
| Debug.logError(errMsg, module); |
| } |
| } |
| } catch (GenericServiceException e1) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.error_setting_security_question", locale); |
| Debug.logError(e1, errMsg, module); |
| } |
| result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS); |
| return result; |
| } |
| |
| /** Updates UserLogin Password info |
| *@param ctx The DispatchContext that this service is operating in |
| *@param context Map containing the input parameters |
| *@return Map with the result of the service, the output parameters |
| */ |
| public static Map<String, Object> updatePassword(DispatchContext ctx, Map<String, ?> context) { |
| Delegator delegator = ctx.getDelegator(); |
| Security security = ctx.getSecurity(); |
| GenericValue loggedInUserLogin = (GenericValue) context.get("userLogin"); |
| Locale locale = (Locale) context.get("locale"); |
| Map<String, Object> result = ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "loginevents.password_was_changed_with_success", locale)); |
| |
| // load the external auth modules -- note: this will only run once and cache the objects |
| if (!AuthHelper.authenticatorsLoaded()) { |
| AuthHelper.loadAuthenticators(ctx.getDispatcher()); |
| } |
| |
| boolean useEncryption = "true".equals(EntityUtilProperties.getPropertyValue("security", "password.encrypt", delegator)); |
| boolean adminUser = false; |
| |
| String userLoginId = (String) context.get("userLoginId"); |
| String errMsg = null; |
| |
| if (UtilValidate.isEmpty(userLoginId)) { |
| userLoginId = loggedInUserLogin.getString("userLoginId"); |
| } |
| |
| // <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. |
| if (!security.hasEntityPermission("PARTYMGR", "_UPDATE", loggedInUserLogin)) { |
| if (!userLoginId.equals(loggedInUserLogin.getString("userLoginId"))) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.not_have_permission_update_password_for_user_login", locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| } else { |
| adminUser = true; |
| } |
| |
| String currentPassword = (String) context.get("currentPassword"); |
| String newPassword = (String) context.get("newPassword"); |
| 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; |
| try { |
| authenticated = AuthHelper.authenticate(userLoginId, currentPassword, true); |
| } catch (AuthenticatorException e) { |
| // safe to ingore this; but we'll log it just in case |
| Debug.logWarning(e, e.getMessage(), module); |
| } |
| |
| // call update password if auth passed |
| if (authenticated) { |
| try { |
| AuthHelper.updatePassword(userLoginId, currentPassword, newPassword); |
| } catch (AuthenticatorException e) { |
| Debug.logError(e, e.getMessage(), module); |
| Map<String, String> messageMap = UtilMisc.toMap("userLoginId", userLoginId); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_userlogin_with_id_not_exist", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| //result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS); |
| result.put("updatedUserLogin", userLoginToUpdate); |
| return result; |
| } else { |
| Map<String, String> messageMap = UtilMisc.toMap("userLoginId", userLoginId); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_userlogin_with_id_not_exist", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| } |
| |
| if ("true".equals(EntityUtilProperties.getPropertyValue("security", "password.lowercase", delegator))) { |
| currentPassword = currentPassword.toLowerCase(); |
| newPassword = newPassword.toLowerCase(); |
| newPasswordVerify = newPasswordVerify.toLowerCase(); |
| } |
| |
| List<String> errorMessageList = new LinkedList<String>(); |
| if (newPassword != null) { |
| checkNewPassword(userLoginToUpdate, currentPassword, newPassword, newPasswordVerify, |
| passwordHint, errorMessageList, adminUser, locale); |
| } |
| |
| if (errorMessageList.size() > 0) { |
| return ServiceUtil.returnError(errorMessageList); |
| } |
| |
| String externalAuthId = userLoginToUpdate.getString("externalAuthId"); |
| if (UtilValidate.isNotEmpty(externalAuthId)) { |
| // external auth is set; don't update the database record |
| try { |
| AuthHelper.updatePassword(externalAuthId, currentPassword, newPassword); |
| } catch (AuthenticatorException e) { |
| Debug.logError(e, e.getMessage(), module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_write_failure", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| } else { |
| userLoginToUpdate.set("currentPassword", useEncryption ? HashCrypt.cryptUTF8(getHashType(), null, newPassword) : newPassword, false); |
| userLoginToUpdate.set("passwordHint", passwordHint, false); |
| userLoginToUpdate.set("requirePasswordChange", "N"); |
| |
| try { |
| userLoginToUpdate.store(); |
| createUserLoginPasswordHistory(delegator,userLoginId, newPassword); |
| } catch (GenericEntityException e) { |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_write_failure", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| } |
| |
| result.put("updatedUserLogin", userLoginToUpdate); |
| return result; |
| } |
| |
| /** Updates the UserLoginId for a party, replicating password, etc from |
| * current login and expiring the old login. |
| *@param ctx The DispatchContext that this service is operating in |
| *@param context Map containing the input parameters |
| *@return Map with the result of the service, the output parameters |
| */ |
| public static Map<String, Object> updateUserLoginId(DispatchContext ctx, Map<String, ?> context) { |
| Map<String, Object> result = new LinkedHashMap<String, Object>(); |
| Delegator delegator = ctx.getDelegator(); |
| GenericValue loggedInUserLogin = (GenericValue) context.get("userLogin"); |
| List<String> errorMessageList = new LinkedList<String>(); |
| Locale locale = (Locale) context.get("locale"); |
| |
| String userLoginId = (String) context.get("userLoginId"); |
| String errMsg = null; |
| |
| if ((userLoginId != null) && ("true".equals(EntityUtilProperties.getPropertyValue("security", "username.lowercase", delegator)))) { |
| userLoginId = userLoginId.toLowerCase(); |
| } |
| |
| String partyId = loggedInUserLogin.getString("partyId"); |
| String password = loggedInUserLogin.getString("currentPassword"); |
| String passwordHint = loggedInUserLogin.getString("passwordHint"); |
| |
| // security: don't create a user login if the specified partyId (if not empty) already exists |
| // unless the logged in user has permission to do so (same partyId or PARTYMGR_CREATE) |
| if (UtilValidate.isNotEmpty(partyId)) { |
| if (!loggedInUserLogin.isEmpty()) { |
| // security check: userLogin partyId must equal partyId, or must have PARTYMGR_CREATE permission |
| if (!partyId.equals(loggedInUserLogin.getString("partyId"))) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.party_with_party_id_exists_not_permission_create_user_login", locale); |
| errorMessageList.add(errMsg); |
| } |
| } else { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.must_logged_in_have_permission_create_user_login_exists", locale); |
| errorMessageList.add(errMsg); |
| } |
| } |
| |
| GenericValue newUserLogin = null; |
| boolean doCreate = true; |
| |
| // check to see if there's a matching login and use it if it's for the same party |
| try { |
| newUserLogin = EntityQuery.use(delegator).from("UserLogin").where("userLoginId", userLoginId).queryOne(); |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_create_login_user_read_failure", messageMap, locale); |
| errorMessageList.add(errMsg); |
| } |
| |
| if (newUserLogin != null) { |
| if (!newUserLogin.get("partyId").equals(partyId)) { |
| Map<String, String> messageMap = UtilMisc.toMap("userLoginId", userLoginId); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_create_login_user_with_ID_exists", messageMap, locale); |
| errorMessageList.add(errMsg); |
| } else { |
| doCreate = false; |
| } |
| } else { |
| newUserLogin = delegator.makeValue("UserLogin", UtilMisc.toMap("userLoginId", userLoginId)); |
| } |
| |
| newUserLogin.set("passwordHint", passwordHint); |
| newUserLogin.set("partyId", partyId); |
| newUserLogin.set("currentPassword", password); |
| newUserLogin.set("enabled", "Y"); |
| newUserLogin.set("disabledDateTime", null); |
| |
| if (errorMessageList.size() > 0) { |
| return ServiceUtil.returnError(errorMessageList); |
| } |
| |
| try { |
| if (doCreate) { |
| newUserLogin.create(); |
| } else { |
| newUserLogin.store(); |
| } |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_create_login_user_write_failure", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| // Deactivate 'old' UserLogin and do not set disabledDateTime here, otherwise the 'old' UserLogin would be reenabled by next login |
| loggedInUserLogin.set("enabled", "N"); |
| loggedInUserLogin.set("disabledDateTime", null); |
| |
| try { |
| loggedInUserLogin.store(); |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_disable_old_login_user_write_failure", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS); |
| result.put("newUserLogin", newUserLogin); |
| return result; |
| } |
| |
| /** Updates UserLogin Security info |
| *@param ctx The DispatchContext that this service is operating in |
| *@param context Map containing the input parameters |
| *@return Map with the result of the service, the output parameters |
| */ |
| public static Map<String, Object> updateUserLoginSecurity(DispatchContext ctx, Map<String, ?> context) { |
| Map<String, Object> result = new LinkedHashMap<String, Object>(); |
| Delegator delegator = ctx.getDelegator(); |
| Security security = ctx.getSecurity(); |
| GenericValue loggedInUserLogin = (GenericValue) context.get("userLogin"); |
| Locale locale = (Locale) context.get("locale"); |
| |
| String userLoginId = (String) context.get("userLoginId"); |
| String errMsg = null; |
| |
| if (UtilValidate.isEmpty(userLoginId)) { |
| userLoginId = loggedInUserLogin.getString("userLoginId"); |
| } |
| |
| // <b>security check</b>: must have PARTYMGR_UPDATE permission |
| if (!security.hasEntityPermission("PARTYMGR", "_UPDATE", loggedInUserLogin) && !security.hasEntityPermission("SECURITY", "_UPDATE", loggedInUserLogin)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.not_permission_update_security_info_for_user_login", locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| 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) { |
| Map<String, String> messageMap = UtilMisc.toMap("userLoginId", userLoginId); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_userlogin_with_id_not_exist", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| boolean wasEnabled = !"N".equals(userLoginToUpdate.get("enabled")); |
| |
| if (context.containsKey("enabled")) { |
| userLoginToUpdate.set("enabled", context.get("enabled"), true); |
| } |
| if (context.containsKey("disabledDateTime")) { |
| userLoginToUpdate.set("disabledDateTime", context.get("disabledDateTime"), true); |
| } |
| if (context.containsKey("successiveFailedLogins")) { |
| userLoginToUpdate.set("successiveFailedLogins", context.get("successiveFailedLogins"), true); |
| } |
| if (context.containsKey("externalAuthId")) { |
| userLoginToUpdate.set("externalAuthId", context.get("externalAuthId"), true); |
| } |
| if (context.containsKey("userLdapDn")) { |
| userLoginToUpdate.set("userLdapDn", context.get("userLdapDn"), true); |
| } |
| if (context.containsKey("requirePasswordChange")) { |
| userLoginToUpdate.set("requirePasswordChange", context.get("requirePasswordChange"), true); |
| } |
| |
| // if was disabled and we are enabling it, clear disabledDateTime |
| if (!wasEnabled && "Y".equals(context.get("enabled"))) { |
| userLoginToUpdate.set("disabledDateTime", null); |
| userLoginToUpdate.set("disabledBy", null); |
| } |
| |
| if ("N".equals(context.get("enabled"))) { |
| userLoginToUpdate.set("disabledBy", loggedInUserLogin.getString("userLoginId")); |
| } |
| |
| try { |
| userLoginToUpdate.store(); |
| } catch (GenericEntityException e) { |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.could_not_change_password_write_failure", messageMap, locale); |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| result.put(ModelService.RESPONSE_MESSAGE, ModelService.RESPOND_SUCCESS); |
| return result; |
| } |
| |
| public static void checkNewPassword(GenericValue userLogin, String currentPassword, String newPassword, String newPasswordVerify, String passwordHint, List<String> errorMessageList, boolean ignoreCurrentPassword, Locale locale) { |
| Delegator delegator = userLogin.getDelegator(); |
| boolean useEncryption = "true".equals(EntityUtilProperties.getPropertyValue("security", "password.encrypt", delegator)); |
| |
| String errMsg = null; |
| |
| if (!ignoreCurrentPassword) { |
| // 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 |
| boolean passwordMatches = checkPassword(userLogin.getString("currentPassword"), useEncryption, currentPassword); |
| if ((currentPassword == null) || (userLogin != null && currentPassword != null && !passwordMatches)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.old_password_not_correct_reenter", locale); |
| errorMessageList.add(errMsg); |
| } |
| if (checkPassword(userLogin.getString("currentPassword"), useEncryption, newPassword)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.new_password_is_equal_to_old_password", locale); |
| errorMessageList.add(errMsg); |
| } |
| |
| } |
| |
| if (UtilValidate.isEmpty(newPassword) || UtilValidate.isEmpty(newPasswordVerify)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_or_verify_missing", locale); |
| errorMessageList.add(errMsg); |
| } else if (!newPassword.equals(newPasswordVerify)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_did_not_match_verify_password", locale); |
| errorMessageList.add(errMsg); |
| } |
| |
| int passwordChangeHistoryLimit = 0; |
| try { |
| passwordChangeHistoryLimit = EntityUtilProperties.getPropertyAsInteger("security", "password.change.history.limit", 0).intValue(); |
| } catch (NumberFormatException nfe) { |
| //No valid value is found so don't bother to save any password history |
| passwordChangeHistoryLimit = 0; |
| } |
| Debug.logInfo(" password.change.history.limit is set to " + passwordChangeHistoryLimit, module); |
| if (passwordChangeHistoryLimit > 0 && userLogin != null) { |
| Debug.logInfo(" checkNewPassword Checking if user is tyring to use old password " + passwordChangeHistoryLimit, module); |
| try { |
| List<GenericValue> pwdHistList = EntityQuery.use(delegator) |
| .from("UserLoginPasswordHistory") |
| .where("userLoginId",userLogin.getString("userLoginId")) |
| .orderBy("-fromDate") |
| .queryList(); |
| for (GenericValue pwdHistValue : pwdHistList) { |
| if (checkPassword(pwdHistValue.getString("currentPassword"), useEncryption, newPassword)) { |
| Map<String, Integer> messageMap = UtilMisc.toMap("passwordChangeHistoryLimit", passwordChangeHistoryLimit); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_must_be_different_from_last_passwords", messageMap, locale); |
| errorMessageList.add(errMsg); |
| break; |
| } |
| } |
| } catch (GenericEntityException e) { |
| Debug.logWarning(e, "", module); |
| Map<String, String> messageMap = UtilMisc.toMap("errorMessage", e.getMessage()); |
| errMsg = UtilProperties.getMessage(resource,"loginevents.error_accessing_password_change_history", messageMap, locale); |
| } |
| |
| } |
| |
| int minPasswordLength = 0; |
| |
| try { |
| minPasswordLength = EntityUtilProperties.getPropertyAsInteger("security", "password.length.min", 0).intValue(); |
| } catch (NumberFormatException nfe) { |
| minPasswordLength = 0; |
| } |
| |
| if (newPassword != null) { |
| // Matching password with pattern |
| String passwordPattern = EntityUtilProperties.getPropertyValue("security", "security.login.password.pattern", "^.*(?=.{5,}).*$", delegator); |
| boolean usePasswordPattern = UtilProperties.getPropertyAsBoolean("security", "security.login.password.pattern.enable", true); |
| if (usePasswordPattern) { |
| Pattern pattern = Pattern.compile(passwordPattern); |
| Matcher matcher = pattern.matcher(newPassword); |
| boolean matched = matcher.matches(); |
| if (!matched) { |
| // This is a mix to handle the OOTB pattern which is only a fixed length |
| Map<String, String> messageMap = UtilMisc.toMap("minPasswordLength", Integer.toString(minPasswordLength)); |
| String passwordPatternMessage = EntityUtilProperties.getPropertyValue("security", |
| "security.login.password.pattern.description", "loginservices.password_must_be_least_characters_long", delegator); |
| errMsg = UtilProperties.getMessage(resource, passwordPatternMessage, messageMap, locale); |
| messageMap = UtilMisc.toMap("passwordPatternMessage", errMsg); |
| errorMessageList.add(errMsg); |
| } |
| } else { |
| if (!(newPassword.length() >= minPasswordLength)) { |
| Map<String, String> messageMap = UtilMisc.toMap("minPasswordLength", Integer.toString(minPasswordLength)); |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_must_be_least_characters_long", messageMap, locale); |
| errorMessageList.add(errMsg); |
| } |
| } |
| if (userLogin != null && newPassword.equalsIgnoreCase(userLogin.getString("userLoginId"))) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_may_not_equal_username", locale); |
| errorMessageList.add(errMsg); |
| } |
| if (UtilValidate.isNotEmpty(passwordHint) && (passwordHint.toUpperCase().indexOf(newPassword.toUpperCase()) >= 0)) { |
| errMsg = UtilProperties.getMessage(resource,"loginservices.password_hint_may_not_contain_password", locale); |
| errorMessageList.add(errMsg); |
| } |
| } |
| } |
| |
| public static String getHashType() { |
| String hashType = UtilProperties.getPropertyValue("security", "password.encrypt.hash.type"); |
| |
| if (UtilValidate.isEmpty(hashType)) { |
| Debug.logWarning("Password encrypt hash type is not specified in security.properties, use SHA", module); |
| hashType = "SHA"; |
| } |
| |
| return hashType; |
| } |
| |
| private static boolean checkPassword(String oldPassword, boolean useEncryption, String currentPassword) { |
| boolean passwordMatches = false; |
| if (oldPassword != null) { |
| if (useEncryption) { |
| passwordMatches = HashCrypt.comparePassword(oldPassword, getHashType(), currentPassword); |
| } else { |
| passwordMatches = oldPassword.equals(currentPassword); |
| } |
| } |
| if (!passwordMatches && "true".equals(UtilProperties.getPropertyValue("security", "password.accept.encrypted.and.plain"))) { |
| passwordMatches = currentPassword.equals(oldPassword); |
| } |
| return passwordMatches; |
| } |
| } |