/****************************************************************************** * Product: iDempiere ERP & CRM Smart Business Solution * * Copyright (C) Trek Global All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. This program is distributed in the hope * * that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * * * @author Juliana Corredor, jcorredor@trekglobal.com * * IDEMPIERE-221 Password reset validation * * http://jira.idempiere.com/browse/IDEMPIERE-221 * * * *****************************************************************************/ package org.compiere.model; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.adempiere.exceptions.AdempiereException; import org.compiere.util.Env; import org.compiere.util.Msg; import org.compiere.util.Util; import org.idempiere.util.HistoryRuleSupportHash; import org.passay.CharacterCharacteristicsRule; import org.passay.CharacterRule; import org.passay.DictionarySubstringRule; import org.passay.EnglishCharacterData; import org.passay.EnglishSequenceData; import org.passay.IllegalSequenceRule; import org.passay.LengthRule; import org.passay.MessageResolver; import org.passay.PasswordData; import org.passay.PasswordGenerator; import org.passay.PasswordValidator; import org.passay.PropertiesMessageResolver; import org.passay.RepeatCharacterRegexRule; import org.passay.Rule; import org.passay.RuleResult; import org.passay.RuleResultDetail; import org.passay.UsernameRule; import org.passay.WhitespaceRule; import org.passay.dictionary.ArrayWordList; import org.passay.dictionary.WordListDictionary; import org.passay.dictionary.WordLists; import org.passay.dictionary.sort.ArraysSort; /** * @author juliana */ public class MPasswordRule extends X_AD_PasswordRule { /** * generated serial id */ private static final long serialVersionUID = 6778305268023192107L; private static final String passay_prefix = "PASSAY_"; /** * UUID based Constructor * @param ctx Context * @param AD_PasswordRule_UU UUID key * @param trxName Transaction */ public MPasswordRule(Properties ctx, String AD_PasswordRule_UU, String trxName) { super(ctx, AD_PasswordRule_UU, trxName); } /** * @param ctx * @param AD_PasswordRule_ID * @param trxName */ public MPasswordRule(Properties ctx, int AD_PasswordRule_ID, String trxName) { super(ctx, AD_PasswordRule_ID, trxName); } /** * @param ctx * @param rs * @param trxName */ public MPasswordRule(Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); } /** * @param ctx * @param trxName * @return password rule */ public static MPasswordRule getRules(Properties ctx, String trxName) { MClient system = MClient.get(ctx, 0); int pwdruleID = system.getAD_PasswordRule_ID(); MPasswordRule pass = null; if (pwdruleID > 0) pass = new MPasswordRule(ctx, pwdruleID, trxName); return pass; } @Override protected boolean beforeSave (boolean newRecord) { // Validate that PathDictionary is mandatory if IsUsingDictionary=Y if (isUsingDictionary()) { StringBuilder msg = new StringBuilder(); if (Util.isEmpty(getPathDictionary())) { msg.append(Msg.getElement(getCtx(), COLUMNNAME_PathDictionary)); } if (msg.length() > 0) { log.saveError("FillMandatory", msg.toString()); return false; } } return true; } /** * Validate newPassword against system password rule. * @param username * @param newPassword * @param passwordHistorys * @throws AdempiereException if newPassword fail password rule validation */ public void validate(String username, String newPassword, List passwordHistorys) throws AdempiereException { ArrayList ruleList = new ArrayList(); if (getMinLength() > 0 || getMaxLength() > 0) { LengthRule lengthRule = new LengthRule(); if (getMinLength() > 0) lengthRule.setMinimumLength(getMinLength()); if (getMaxLength() > 0) lengthRule.setMaximumLength(getMaxLength()); ruleList.add(lengthRule); } if (isWhitespace()) { ruleList.add(new WhitespaceRule()); } // control allowed characters CharacterCharacteristicsRule charRule = new CharacterCharacteristicsRule(); int numValidations = 0; if (getDigitCharacter() > 0) { // require at least n digit in passwords numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.Digit, getDigitCharacter())); } if (getNonAlphaNumericCharacter() > 0) { // require at least n non-alphanumeric char numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.Special, getNonAlphaNumericCharacter())); } if (getUppercaseCharacter() > 0) { numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, getUppercaseCharacter())); } if (getLowercaseCharacter() > 0) { numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, getLowercaseCharacter())); } if (getAlphabeticalCharacter() > 0){ numValidations++; charRule.getRules().add(new CharacterRule (EnglishCharacterData.Alphabetical, getAlphabeticalCharacter())); } if (! charRule.getRules().isEmpty()) { charRule.setNumberOfCharacteristics(numValidations); ruleList.add(charRule); } if (getAlphabeticalSequence() > 0) { ruleList.add(new IllegalSequenceRule (EnglishSequenceData.Alphabetical, getAlphabeticalSequence(), true)); } if (getNumericalSequence() > 0) { ruleList.add(new IllegalSequenceRule(EnglishSequenceData.Numerical, getNumericalSequence(), true)); } if (getQWERTYSequence() > 0) { ruleList.add(new IllegalSequenceRule(EnglishSequenceData.USQwerty, getQWERTYSequence(), true)); } if (getRepeatCharacterRegex() > 0) { ruleList.add(new RepeatCharacterRegexRule(getRepeatCharacterRegex())); } if (isUserNameRule()) { ruleList.add(new UsernameRule(true, true)); } if (isUsingDictionary()) { if (getPathDictionary().length() > 0) { try { ArrayWordList awl = WordLists.createFromReader( new FileReader[] {new FileReader(getPathDictionary())}, true, new ArraysSort()); WordListDictionary dict = new WordListDictionary(awl); DictionarySubstringRule dictRule = new DictionarySubstringRule(dict); if (isDictMatchBackwards()) { dictRule.setMatchBackwards(true); // match dictionary words backwards } ruleList.add(dictRule); } catch (FileNotFoundException e) { throw new AdempiereException("Could not find dictionary file: " + e.getMessage()); } catch (IOException e) { throw new AdempiereException("Could not find dictionary file: " + e.getMessage()); } } } // history password check List historyData = new ArrayList(); for (MPasswordHistory passwordHistory : passwordHistorys){ historyData.add(new PasswordData.HistoricalReference(passwordHistory.getSalt(), passwordHistory.getPassword())); } HistoryRuleSupportHash historyRule = new HistoryRuleSupportHash(); ruleList.add(historyRule); // validator all rule if (!ruleList.isEmpty()) { PasswordValidator validator = new PasswordValidator(getCustomResolver(), ruleList); PasswordData passwordData = new PasswordData(username, newPassword); passwordData.setPasswordReferences(historyData); RuleResult result = validator.validate(passwordData); if (!result.isValid()) { StringBuilder error = new StringBuilder(Msg.getMsg(getCtx(), "PasswordErrors")); for(RuleResultDetail detail : result.getDetails()){ error.append("\n- ").append(resolveMessage(detail)); } throw new AdempiereException(error.toString()); } } } /** * Resolve & translate validation message returned by Passay * @param detail * @return error message */ private String resolveMessage(final RuleResultDetail detail) { final String key = passay_prefix + detail.getErrorCode(); final String message = Msg.translate(Env.getAD_Language(getCtx()), key); String format; if (!message.equals(key)) { format = String.format(message, detail.getValues()); } else { if (!detail.getParameters().isEmpty()) { format = String.format("%s:%s", key, detail.getParameters()); } else { format = String.format("%s", key); } } return format; } /** * @return MessageResolver */ private MessageResolver getCustomResolver() { Properties props = null; InputStream in = null; try { StringBuilder file = new StringBuilder("vtpassword_messages_").append(Env.getLoginLanguage(getCtx()).getLocale().getLanguage()) .append(".properties"); in = this.getClass().getResourceAsStream(file.toString()); if (in != null) { props = new Properties(); props.load(in); } } catch (Exception e) { props = null; } finally{ if (in != null) { try { in.close(); } catch (Exception e2) {} in = null; } } if (props == null) return new PropertiesMessageResolver(); else return new PropertiesMessageResolver(props); } /** * Generate new password * @return new password generated */ public String generate() { CharacterCharacteristicsRule charRule = new CharacterCharacteristicsRule(); int numValidations = 0; if (getDigitCharacter() > 0) { // require at least n digit in passwords numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.Digit, getDigitCharacter())); } if (getNonAlphaNumericCharacter() > 0) { // require at least n non-alphanumeric char numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.Special, getNonAlphaNumericCharacter())); } if (getUppercaseCharacter() > 0) { numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, getUppercaseCharacter())); } if (getLowercaseCharacter() > 0) { numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, getLowercaseCharacter())); } if (getAlphabeticalCharacter() > 0){ numValidations++; charRule.getRules().add(new CharacterRule(EnglishCharacterData.Alphabetical, getAlphabeticalCharacter())); } if (! charRule.getRules().isEmpty()) { charRule.setNumberOfCharacteristics(numValidations); } int len = 10; // suggested length to generate if (len < getMinLength()) { len = getMinLength(); } if (len > getMaxLength()) { len = getMaxLength(); } return new PasswordGenerator().generatePassword(len, charRule.getRules()); } }