/** * $Id: AcountValidationLocator.java 132392 2013-12-10 00:03:47Z matthew@longsight.com $ * $URL: https://source.sakaiproject.org/svn/reset-pass/trunk/account-validator-tool/src/java/org/sakaiproject/accountvalidator/tool/otp/AcountValidationLocator.java $ * ************************************************************************** * Copyright (c) 2008, 2009 The Sakai Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.accountvalidator.tool.otp; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.ISODateTimeFormat; import org.sakaiproject.accountvalidator.logic.ValidationLogic; import org.sakaiproject.accountvalidator.model.ValidationAccount; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.entity.api.ResourcePropertiesEdit; import org.sakaiproject.entitybroker.DeveloperHelperService; import org.sakaiproject.entitybroker.EntityReference; import org.sakaiproject.event.api.UsageSessionService; import org.sakaiproject.user.api.Authentication; import org.sakaiproject.user.api.AuthenticationException; import org.sakaiproject.user.api.AuthenticationManager; import org.sakaiproject.user.api.Evidence; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserAlreadyDefinedException; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserEdit; import org.sakaiproject.user.api.UserLockedException; import org.sakaiproject.user.api.UserNotDefinedException; import org.sakaiproject.user.api.UserPermissionException; import org.sakaiproject.util.ExternalTrustedEvidence; import uk.org.ponder.beanutil.BeanLocator; import uk.org.ponder.messageutil.TargettedMessage; import uk.org.ponder.messageutil.TargettedMessageList; public class AcountValidationLocator implements BeanLocator { private static Log log = LogFactory.getLog(AcountValidationLocator.class); public static final String NEW_PREFIX = "new"; public static final String UNKOWN_PREFIX = "unkown"; private Map<String, Object> delivered = new HashMap<String, Object>(); //For calculating password entropy public static final char[] CHAR_LOWERS = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; public static final char[] CHAR_UPPERS = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; public static final char[] CHAR_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; public static final char[] CHAR_SPECIALS = { '!', '$', '*', '+', '-', '.', '=', '?', '@', '^', '_', '|', '~' }; private ValidationLogic validationLogic; public void setValidationLogic(ValidationLogic vl) { validationLogic = vl; } private UserDirectoryService userDirectoryService; public void setUserDirectoryService(UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } private AuthenticationManager authenticationManager; public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } private SecurityService securityService; public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } private UsageSessionService usageSessionService; public void setUsageSessionService(UsageSessionService usageSessionService) { this.usageSessionService = usageSessionService; } private HttpServletRequest httpServletRequest; public void setHttpServletRequest(HttpServletRequest httpServletRequest) { this.httpServletRequest = httpServletRequest; } private TargettedMessageList tml; public void setTargettedMessageList(TargettedMessageList tml) { this.tml = tml; } private DeveloperHelperService developerHelperService; public void setDeveloperHelperService( DeveloperHelperService developerHelperService) { this.developerHelperService = developerHelperService; } private ServerConfigurationService serverConfigurationService; public void setServerConfigurationService( ServerConfigurationService serverConfigurationService) { this.serverConfigurationService = serverConfigurationService; } public Object locateBean(String name) { Object togo = delivered.get(name); log.debug("Locating ValidationAccount: " + name); if (togo == null){ if(name.startsWith(NEW_PREFIX)){ togo = new ValidationAccount(); } else if (name.startsWith(UNKOWN_PREFIX)) { togo = validationLogic.getVaLidationAcountBytoken(name); }else { //find the bean try { log.debug("Locating bean: " + name); Long id = Long.valueOf(name); togo = validationLogic.getVaLidationAcountById(id); } catch (NumberFormatException nfe) { return null; } } delivered.put(name, togo); } return togo; } public void saveAll() { log.debug("SaveAll() for " + delivered.size()); for (Iterator<String> it = delivered.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); ValidationAccount item = (ValidationAccount) delivered.get(key); /* if (key.startsWith(NEW_PREFIX)) { // could be a special case in some models }*/ log.debug("Saving Item: " + item.getId() + " for user: " + item.getUserId()); //validationLogic. } } public ValidationAccount getAccount() { log.debug("getAccount for " + delivered.size()); for (Iterator<String> it = delivered.keySet().iterator(); it.hasNext();) { String key = it.next(); ValidationAccount item = (ValidationAccount) delivered.get(key); //this will be a new with only a key set ValidationAccount ret = null; if (item.getValidationToken() != null) { ret = validationLogic.getVaLidationAcountBytoken(item.getValidationToken()); if (ret == null) { Object[] args = new Object[]{ item.getValidationToken()}; tml.addMessage(new TargettedMessage("msg.noSuchValidation", args, TargettedMessage.SEVERITY_ERROR)); } } else { tml.addMessage(new TargettedMessage("msg.noCode", new Object[]{}, TargettedMessage.SEVERITY_ERROR)); } return ret; } return null; } //TODO the logic should be moved to a service method public String validateAccount() { log.debug("Validate Account"); for (Iterator<String> it = delivered.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); ValidationAccount item = (ValidationAccount) delivered.get(key); if (ValidationAccount.STATUS_CONFIRMED.equals(item.getStatus()) || ValidationAccount.STATUS_EXPIRED.equals(item.getStatus())) { return "error"; } log.debug("Validating Item: " + item.getId() + " for user: " + item.getUserId()); String firstName = item.getFirstName(); String surname = item.getSurname(); log.debug(firstName + " " + surname); log.debug("this is an new item?: " + item.getAccountStatus()); try { String userId = EntityReference.getIdFromRef(item.getUserId()); //we need permission to edit this user //if this is an existing user did the password match? if (ValidationAccount.ACCOUNT_STATUS_EXISITING== item.getAccountStatus() && !validateLogin(userId, item.getPassword())) { tml.addMessage(new TargettedMessage("validate.invalidPassword", new Object[]{}, TargettedMessage.SEVERITY_ERROR)); return "error"; } securityService.pushAdvisor(new SecurityAdvisor() { public SecurityAdvice isAllowed(String userId, String function, String reference) { if (function.equals(UserDirectoryService.SECURE_UPDATE_USER_ANY)) { return SecurityAdvice.ALLOWED; } else { return SecurityAdvice.NOT_ALLOWED; } } }); UserEdit u = userDirectoryService.editUser(userId); u.setFirstName(firstName); u.setLastName(surname); ResourcePropertiesEdit rp = u.getPropertiesEdit(); DateTime dt = new DateTime(); DateTimeFormatter fmt = ISODateTimeFormat.dateTime(); rp.addProperty("AccountValidated", fmt.print(dt)); //if this is a new account set the password if (ValidationAccount.ACCOUNT_STATUS_NEW == item.getAccountStatus() || ValidationAccount.ACCOUNT_STATUS_LEGACY_NOPASS == item.getAccountStatus() || ValidationAccount.ACCOUNT_STATUS_PASSWORD_RESET == item.getAccountStatus()) { if (item.getPassword() == null || !item.getPassword().equals(item.getPassword2())) { //Abandon the edit userDirectoryService.cancelEdit(u); tml.addMessage(new TargettedMessage("validate.passNotMatch", new Object[]{}, TargettedMessage.SEVERITY_ERROR)); return "error!"; } if (serverConfigurationService.getBoolean("account-validator.validate.passwords", false)) { //verify that the password strength is sufficient String displayId = u.getDisplayId(); int minimumEntropy = serverConfigurationService.getInt("account-validator.minimum.password.entropy", 16); if (!passwordIsStrong(item.getPassword(), displayId, minimumEntropy)) { userDirectoryService.cancelEdit(u); tml.addMessage(new TargettedMessage("validate.tooWeak", new Object[]{}, TargettedMessage.SEVERITY_ERROR)); return "error!"; } } u.setPassword(item.getPassword()); // Do they have to accept terms and conditions. if (!"".equals(serverConfigurationService.getString("account-validator.terms"))) { // Check they accepted the terms. if (item.getTerms().booleanValue()) { u.getPropertiesEdit().addProperty("TermsAccepted", "true"); } else { userDirectoryService.cancelEdit(u); tml.addMessage(new TargettedMessage("validate.acceptTerms", new Object[]{}, TargettedMessage.SEVERITY_ERROR)); return "error!"; } } } userDirectoryService.commitEdit(u); //update the Validation object item.setvalidationReceived(new Date()); item.setStatus(ValidationAccount.STATUS_CONFIRMED); log.debug("Saving now ..."); //post an event developerHelperService.fireEvent("accountvalidation.validated", u.getReference()); validationLogic.save(item); //log the user in Evidence e = new ExternalTrustedEvidence(u.getEid()); try { Authentication a = authenticationManager.authenticate(e); log.debug("authenticated " + a.getEid() + "(" + a.getUid() + ")"); log.debug("reg: " + httpServletRequest.getRemoteAddr()); log.debug("user agent: " + httpServletRequest.getHeader("user-agent")); usageSessionService.login(a , httpServletRequest); } catch (AuthenticationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } catch (UserNotDefinedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UserPermissionException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UserLockedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UserAlreadyDefinedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { securityService.popAdvisor(); } //validationLogic. } return "success"; } /** * To verify that a password's strength is sufficient. * Based on verifyPasswordStrength() in * http://grepcode.com/file/repo1.maven.org/maven2/org.owasp.esapi/esapi/2.0_rc10/org/owasp/esapi/reference/FileBasedAuthenticator.java * @param password * The password whose strength is being checked * @param displayId * The user's displayId, which the password will be compared against. Skips this check if null * @param minimumEntropy * The minimum allowed entropy for the password * @return true if the entropy calculated by this algorithm is greater than minimumEntropy, false otherwise */ public boolean passwordIsStrong(String password, String displayId, int minimumEntropy) { if (password == null) { return false; } if (displayId != null) { int length = displayId.length(); for (int i = 0; i < length -2; i++) { String sub = displayId.substring(i, i + 3); if (password.indexOf(sub) > -1) { return false; } } } // new password must have enough character sets and length int charsets = 0; for (int i = 0; i < password.length(); i++) { if (Arrays.binarySearch(CHAR_LOWERS, password.charAt(i)) >= 0) { charsets++; break; } } for (int i = 0; i < password.length(); i++) { if (Arrays.binarySearch(CHAR_UPPERS, password.charAt(i)) >= 0) { charsets++; break; } } for (int i = 0; i < password.length(); i++) { if (Arrays.binarySearch(CHAR_DIGITS, password.charAt(i)) >= 0) { charsets++; break; } } for (int i = 0; i < password.length(); i++) { if (Arrays.binarySearch(CHAR_SPECIALS, password.charAt(i)) >= 0) { charsets++; break; } } // calculate and verify password strength int strength = password.length() * charsets; if (strength < minimumEntropy) { return false; } return true; } private boolean validateLogin(String userId, String password) { try { User u = userDirectoryService.authenticate(userDirectoryService.getUserEid(userId), password); if (u != null) { return true; } } catch (UserNotDefinedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return false; } }