diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/PasswordPolicyUtils.java b/infra/common/src/main/java/com/evolveum/midpoint/common/policy/PasswordPolicyUtils.java index e0dc54ec824..066378d28d1 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/PasswordPolicyUtils.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/policy/PasswordPolicyUtils.java @@ -15,9 +15,9 @@ */ package com.evolveum.midpoint.common.policy; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; @@ -28,6 +28,7 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CharacterClassType; import com.evolveum.midpoint.xml.ns._public.common.common_3.LimitationsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordLifeTimeType; import com.evolveum.midpoint.xml.ns._public.common.common_3.StringLimitType; @@ -43,8 +44,8 @@ public class PasswordPolicyUtils { private static final transient Trace LOGGER = TraceManager.getTrace(PasswordPolicyUtils.class); - private static final String DOT_CLASS = PasswordPolicyUtils.class.getName() + "."; - private static final String OPERATION_PASSWORD_VALIDATION = DOT_CLASS + "passwordValidation"; + private static final String DOT_CLASS = PasswordPolicyUtils.class.getName() + "."; + private static final String OPERATION_PASSWORD_VALIDATION = DOT_CLASS + "passwordValidation"; /** * add defined default values @@ -75,8 +76,6 @@ public static void normalize(ValuePolicyType pp) { return; } - - /** * Check provided password against provided policy * @@ -90,48 +89,50 @@ public static void normalize(ValuePolicyType pp) { * met */ - public static boolean validatePassword(String password, List policies, OperationResult result) { - boolean ret=true; - //iterate through policies - for (ValuePolicyType pp: policies) { - OperationResult op = validatePassword(password, pp); + public static boolean validatePassword(String password, List historyEntries, List policies, + OperationResult result) { + boolean ret = true; + // iterate through policies + for (ValuePolicyType pp : policies) { + OperationResult op = validatePassword(password, historyEntries, pp); result.addSubresult(op); - //if one fail then result is failure - if (ret == true && ! op.isSuccess()) { + // if one fail then result is failure + if (ret == true && !op.isSuccess()) { ret = false; } } return ret; } - - public static boolean validatePassword(String password, List> policies) { - boolean ret=true; - //iterate through policies - for (PrismObject pp: policies) { - OperationResult op = validatePassword(password, pp.asObjectable()); -// result.addSubresult(op); - //if one fail then result is failure - if (ret == true && ! op.isSuccess()) { + + public static boolean validatePassword(String password, List historyEntries, List> policies) { + boolean ret = true; + // iterate through policies + for (PrismObject pp : policies) { + OperationResult op = validatePassword(password, historyEntries, pp.asObjectable()); + // result.addSubresult(op); + // if one fail then result is failure + if (ret == true && !op.isSuccess()) { ret = false; } } return ret; } - - public static boolean validatePassword(ProtectedStringType password, List> policies) { - boolean ret=true; - //iterate through policies - for (PrismObject pp: policies) { - OperationResult op = validatePassword(password.getClearValue(), pp.asObjectable()); -// result.addSubresult(op); - //if one fail then result is failure - if (ret == true && ! op.isSuccess()) { + + public static boolean validatePassword(ProtectedStringType password, List historyEntries, + List> policies) { + boolean ret = true; + // iterate through policies + for (PrismObject pp : policies) { + OperationResult op = validatePassword(password.getClearValue(), historyEntries, pp.asObjectable()); + // result.addSubresult(op); + // if one fail then result is failure + if (ret == true && !op.isSuccess()) { ret = false; } } return ret; } - + /** * Check provided password against provided policy * @@ -145,12 +146,12 @@ public static boolean validatePassword(ProtectedStringType password, List historyEntries, ValuePolicyType pp, OperationResult result) { + OperationResult op = validatePassword(password, historyEntries, pp); result.addSubresult(op); return op.isSuccess(); } - + /** * Check provided password against provided policy * @@ -160,199 +161,259 @@ public static boolean validatePassword(String password, ValuePolicyType pp, Oper * - Password policy used * @return - Operation result of this validation */ - public static OperationResult validatePassword(String password, ValuePolicyType pp) { + public static OperationResult validatePassword(String password, List historyEntries, ValuePolicyType pp) { Validate.notNull(pp, "Password policy must not be null."); - - OperationResult ret = new OperationResult(OPERATION_PASSWORD_VALIDATION); - ret.addParam("policyName", pp.getName()); + + OperationResult ret = new OperationResult(OPERATION_PASSWORD_VALIDATION); + ret.addParam("policyName", pp.getName()); normalize(pp); - - if (password == null && pp.getMinOccurs() != null && XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()) == 0) { + + if (password == null && pp.getMinOccurs() != null + && XsdTypeMapper.multiplicityToInteger(pp.getMinOccurs()) == 0) { // No password is allowed ret.recordSuccess(); return ret; } - + if (password == null) { password = ""; } - + LimitationsType lims = pp.getStringPolicy().getLimitations(); StringBuilder message = new StringBuilder(); - - // Test minimal length - if (lims.getMinLength() == null){ - lims.setMinLength(0); - } - if (lims.getMinLength() > password.length()) { - String msg = "Required minimal size (" + lims.getMinLength() + ") of password is not met (password length: " - + password.length() + ")"; - ret.addSubresult(new OperationResult("Check global minimal length", OperationResultStatus.FATAL_ERROR, - msg)); - message.append(msg); - message.append("\n"); - } -// else { -// ret.addSubresult(new OperationResult("Check global minimal length. Minimal length of password OK.", -// OperationResultStatus.SUCCESS, "PASSED")); -// } - // Test maximal length - if (lims.getMaxLength() != null) { - if (lims.getMaxLength() < password.length()) { - String msg = "Required maximal size (" + lims.getMaxLength() - + ") of password was exceeded (password length: " + password.length() + ")."; - ret.addSubresult(new OperationResult("Check global maximal length", OperationResultStatus.FATAL_ERROR, - msg)); - message.append(msg); - message.append("\n"); - } -// else { -// ret.addSubresult(new OperationResult("Check global maximal length. Maximal length of password OK.", -// OperationResultStatus.SUCCESS, "PASSED")); -// } - } - // Test uniqueness criteria - HashSet tmp = new HashSet(StringPolicyUtils.stringTokenizer(password)); - if (lims.getMinUniqueChars() != null) { - if (lims.getMinUniqueChars() > tmp.size()) { - String msg = "Required minimal count of unique characters (" - + lims.getMinUniqueChars() - + ") in password are not met (unique characters in password " + tmp.size() + ")"; - ret.addSubresult(new OperationResult("Check minimal count of unique chars", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } -// else { -// ret.addSubresult(new OperationResult( -// "Check minimal count of unique chars. Password satisfies minimal required unique characters.", -// OperationResultStatus.SUCCESS, "PASSED")); -// } - } + testMinimalLength(password, lims, ret, message); + testMaximalLength(password, lims, ret, message); - // check limitation - HashSet allValidChars = new HashSet(128); - ArrayList validChars = null; - ArrayList passwd = StringPolicyUtils.stringTokenizer(password); + testMinimalUniqueCharacters(password, lims, ret, message); + testPasswordHistoryEntries(password, historyEntries, ret, message); - if (lims.getLimit() == null || lims.getLimit().isEmpty()){ - if (message.toString() == null || message.toString().isEmpty()){ + + if (lims.getLimit() == null || lims.getLimit().isEmpty()) { + if (message.toString() == null || message.toString().isEmpty()) { ret.computeStatus(); } else { ret.computeStatus(message.toString()); - + } - + return ret; } - for (StringLimitType l : lims.getLimit()) { - OperationResult limitResult = new OperationResult("Tested limitation: " + l.getDescription()); - if (null != l.getCharacterClass().getValue()) { - validChars = StringPolicyUtils.stringTokenizer(l.getCharacterClass().getValue()); - } else { - validChars = StringPolicyUtils.stringTokenizer(StringPolicyUtils.collectCharacterClass(pp - .getStringPolicy().getCharacterClass(), l.getCharacterClass().getRef())); - } - // memorize validChars + + // check limitation + HashSet validChars = null; + HashSet allValidChars = new HashSet<>(); + List passwd = StringPolicyUtils.stringTokenizer(password); + for (StringLimitType stringLimitationType : lims.getLimit()) { + OperationResult limitResult = new OperationResult( + "Tested limitation: " + stringLimitationType.getDescription()); + + validChars = getValidCharacters(stringLimitationType.getCharacterClass(), pp); + int count = countValidCharacters(validChars, passwd); allValidChars.addAll(validChars); + testMinimalOccurence(stringLimitationType, count, limitResult, message); + testMaximalOccurence(stringLimitationType, count, limitResult, message); + testMustBeFirst(stringLimitationType, count, limitResult, message, password, validChars); - // Count how many character for this limitation are there - int count = 0; - for (String s : passwd) { - if (validChars.contains(s)) { - count++; - } - } + limitResult.computeStatus(); + ret.addSubresult(limitResult); + } + testInvalidCharacters(passwd, allValidChars, ret, message); + + if (message.toString() == null || message.toString().isEmpty()) { + ret.computeStatus(); + } else { + ret.computeStatus(message.toString()); + + } + + return ret; + } + + private static void testPasswordHistoryEntries(String password, List historyEntries, + OperationResult result, StringBuilder message) { + + if (historyEntries == null || historyEntries.isEmpty()) { + return; + } + + if (historyEntries.contains(password)) { + String msg = "Password couldn't be changed to the same value. Please select another password."; + result.addSubresult(new OperationResult("Check if password does not contain invalid characters", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + + } + + private static void testInvalidCharacters(List password, HashSet validChars, + OperationResult result, StringBuilder message) { - // Test minimal occurrence - if (l.getMinOccurs() == null){ - l.setMinOccurs(0); + // Check if there is no invalid character + StringBuilder invalidCharacters = new StringBuilder(); + for (String s : password) { + if (!validChars.contains(s)) { + // memorize all invalid characters + invalidCharacters.append(s); } - if (l.getMinOccurs() > count) { - String msg = "Required minimal occurrence (" + l.getMinOccurs() - + ") of characters ("+l.getDescription()+") in password is not met (occurrence of characters in password " - + count + ")."; - limitResult.addSubresult(new OperationResult("Check minimal occurrence of characters", + } + if (invalidCharacters.length() > 0) { + String msg = "Characters [ " + invalidCharacters + " ] are not allowed in password"; + result.addSubresult(new OperationResult("Check if password does not contain invalid characters", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + // else { + // ret.addSubresult(new OperationResult("Check if password does not + // contain invalid characters OK.", + // OperationResultStatus.SUCCESS, "PASSED")); + // } + + } + + private static void testMustBeFirst(StringLimitType stringLimitationType, int count, + OperationResult limitResult, StringBuilder message, String password, Set validChars) { + // test if first character is valid + if (stringLimitationType.isMustBeFirst() == null) { + stringLimitationType.setMustBeFirst(false); + } + // we check mustBeFirst only for non-empty passwords + if (StringUtils.isNotEmpty(password) && stringLimitationType.isMustBeFirst() + && !validChars.contains(password.substring(0, 1))) { + String msg = "First character is not from allowed set. Allowed set: " + validChars.toString(); + limitResult.addSubresult( + new OperationResult("Check valid first char", OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + // else { + // limitResult.addSubresult(new OperationResult("Check valid first char + // in password OK.", + // OperationResultStatus.SUCCESS, "PASSED")); + // } + + } + + private static void testMaximalOccurence(StringLimitType stringLimitationType, int count, + OperationResult limitResult, StringBuilder message) { + // Test maximal occurrence + if (stringLimitationType.getMaxOccurs() != null) { + + if (stringLimitationType.getMaxOccurs() < count) { + String msg = "Required maximal occurrence (" + stringLimitationType.getMaxOccurs() + + ") of characters (" + stringLimitationType.getDescription() + + ") in password was exceeded (occurrence of characters in password " + count + ")."; + limitResult.addSubresult(new OperationResult("Check maximal occurrence of characters", OperationResultStatus.FATAL_ERROR, msg)); message.append(msg); message.append("\n"); - } -// else { -// limitResult.addSubresult(new OperationResult("Check minimal occurrence of characters in password OK.", -// OperationResultStatus.SUCCESS, "PASSED")); -// } - - // Test maximal occurrence - if (l.getMaxOccurs() != null) { - - if (l.getMaxOccurs() < count) { - String msg = "Required maximal occurrence (" + l.getMaxOccurs() - + ") of characters ("+l.getDescription()+") in password was exceeded (occurrence of characters in password " - + count + ")."; - limitResult.addSubresult(new OperationResult("Check maximal occurrence of characters", - OperationResultStatus.FATAL_ERROR, msg)); - message.append(msg); - message.append("\n"); - } -// else { -// limitResult.addSubresult(new OperationResult( -// "Check maximal occurrence of characters in password OK.", OperationResultStatus.SUCCESS, -// "PASSED")); -// } } - // test if first character is valid - if (l.isMustBeFirst() == null){ - l.setMustBeFirst(false); + // else { + // limitResult.addSubresult(new OperationResult( + // "Check maximal occurrence of characters in password OK.", + // OperationResultStatus.SUCCESS, + // "PASSED")); + // } + } + + } + + private static void testMinimalOccurence(StringLimitType stringLimitation, int count, + OperationResult result, StringBuilder message) { + // Test minimal occurrence + if (stringLimitation.getMinOccurs() == null) { + stringLimitation.setMinOccurs(0); + } + if (stringLimitation.getMinOccurs() > count) { + String msg = "Required minimal occurrence (" + stringLimitation.getMinOccurs() + + ") of characters (" + stringLimitation.getDescription() + + ") in password is not met (occurrence of characters in password " + count + ")."; + result.addSubresult(new OperationResult("Check minimal occurrence of characters", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } + } + + private static int countValidCharacters(Set validChars, List password) { + int count = 0; + for (String s : password) { + if (validChars.contains(s)) { + count++; } - // we check mustBeFirst only for non-empty passwords - if (StringUtils.isNotEmpty(password) && l.isMustBeFirst() && !validChars.contains(password.substring(0, 1))) { - String msg = "First character is not from allowed set. Allowed set: " - + validChars.toString(); - limitResult.addSubresult(new OperationResult("Check valid first char", + } + return count; + } + + private static HashSet getValidCharacters(CharacterClassType characterClassType, + ValuePolicyType passwordPolicy) { + if (null != characterClassType.getValue()) { + return new HashSet(StringPolicyUtils.stringTokenizer(characterClassType.getValue())); + } else { + return new HashSet(StringPolicyUtils.stringTokenizer(StringPolicyUtils + .collectCharacterClass(passwordPolicy.getStringPolicy().getCharacterClass(), + characterClassType.getRef()))); + } + } + + private static void testMinimalUniqueCharacters(String password, LimitationsType limitations, + OperationResult result, StringBuilder message) { + // Test uniqueness criteria + HashSet tmp = new HashSet(StringPolicyUtils.stringTokenizer(password)); + if (limitations.getMinUniqueChars() != null) { + if (limitations.getMinUniqueChars() > tmp.size()) { + String msg = "Required minimal count of unique characters (" + limitations.getMinUniqueChars() + + ") in password are not met (unique characters in password " + tmp.size() + ")"; + result.addSubresult(new OperationResult("Check minimal count of unique chars", OperationResultStatus.FATAL_ERROR, msg)); message.append(msg); message.append("\n"); - } -// else { -// limitResult.addSubresult(new OperationResult("Check valid first char in password OK.", -// OperationResultStatus.SUCCESS, "PASSED")); -// } - limitResult.computeStatus(); - ret.addSubresult(limitResult); + } + } + } - // Check if there is no invalid character - StringBuilder sb = new StringBuilder(); - for (String s : passwd) { - if (!allValidChars.contains(s)) { - // memorize all invalid characters - sb.append(s); - } + private static void testMinimalLength(String password, LimitationsType limitations, + OperationResult result, StringBuilder message) { + // Test minimal length + if (limitations.getMinLength() == null) { + limitations.setMinLength(0); } - if (sb.length() > 0) { - String msg = "Characters [ " + sb - + " ] are not allowed in password"; - ret.addSubresult(new OperationResult("Check if password does not contain invalid characters", + if (limitations.getMinLength() > password.length()) { + String msg = "Required minimal size (" + limitations.getMinLength() + + ") of password is not met (password length: " + password.length() + ")"; + result.addSubresult(new OperationResult("Check global minimal length", OperationResultStatus.FATAL_ERROR, msg)); message.append(msg); message.append("\n"); - } -// else { -// ret.addSubresult(new OperationResult("Check if password does not contain invalid characters OK.", -// OperationResultStatus.SUCCESS, "PASSED")); -// } + } + } - if (message.toString() == null || message.toString().isEmpty()){ - ret.computeStatus(); - } else { - ret.computeStatus(message.toString()); - + private static void testMaximalLength(String password, LimitationsType limitations, + OperationResult result, StringBuilder message) { + // Test maximal length + if (limitations.getMaxLength() != null) { + if (limitations.getMaxLength() < password.length()) { + String msg = "Required maximal size (" + limitations.getMaxLength() + + ") of password was exceeded (password length: " + password.length() + ")."; + result.addSubresult(new OperationResult("Check global maximal length", + OperationResultStatus.FATAL_ERROR, msg)); + message.append(msg); + message.append("\n"); + } } - - return ret; + } + + private static void buildMessageAndResult(StringBuilder messageBuilder, String message, + String operationSummary, OperationResult result) { + messageBuilder.append(message); + messageBuilder.append("\n"); + result.addSubresult(new OperationResult("Check global maximal length", + OperationResultStatus.FATAL_ERROR, message)); } } diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java b/infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java index ce855e4ebae..a499774ca46 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/policy/StringPolicyUtils.java @@ -17,6 +17,8 @@ import java.util.ArrayList; import java.util.HashSet; +import java.util.List; +import java.util.Set; import javax.xml.namespace.QName; @@ -102,13 +104,13 @@ public static String collectCharacterClass(CharacterClassType cc, QName ref) { return new StrBuilder().appendAll(h).toString(); } - /** + /**z * Convert string to array * @param in * @return ArrayList */ - public static ArrayList stringTokenizer(String in) { - ArrayList l = new ArrayList(); + public static List stringTokenizer(String in) { + List l = new ArrayList(); for (String a: in.split("")) { if (!a.isEmpty()) { l.add(a); diff --git a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java b/infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java index f0641060462..e67fcc04b6c 100644 --- a/infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java +++ b/infra/common/src/main/java/com/evolveum/midpoint/common/policy/ValuePolicyGenerator.java @@ -26,6 +26,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Random; import javax.xml.namespace.QName; @@ -77,7 +79,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // Optimize usage of limits ass hashmap of limitas and key is set of // valid chars for each limitation - HashMap> lims = new HashMap>(); + Map> lims = new HashMap>(); for (StringLimitType l : policy.getLimitations().getLimit()) { if (null != l.getCharacterClass().getValue()) { lims.put(l, StringPolicyUtils.stringTokenizer(l.getCharacterClass().getValue())); @@ -128,7 +130,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea /* ********************************** * Try to find best characters to be first in password */ - HashMap> mustBeFirst = new HashMap>(); + Map> mustBeFirst = new HashMap>(); for (StringLimitType l : lims.keySet()) { if (l.isMustBeFirst() != null && l.isMustBeFirst()) { mustBeFirst.put(l, lims.get(l)); @@ -137,10 +139,10 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // If any limitation was found to be first if (!mustBeFirst.isEmpty()) { - HashMap> posibleFirstChars = cardinalityCounter(mustBeFirst, null, false, false, + Map> posibleFirstChars = cardinalityCounter(mustBeFirst, null, false, false, generatorResult); int intersectionCardinality = mustBeFirst.keySet().size(); - ArrayList intersectionCharacters = posibleFirstChars.get(intersectionCardinality); + List intersectionCharacters = posibleFirstChars.get(intersectionCardinality); // If no intersection was found then raise error if (null == intersectionCharacters || intersectionCharacters.size() == 0) { generatorResult @@ -178,7 +180,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea boolean uniquenessReached = false; // Count cardinality of elements - HashMap> chars; + Map> chars; for (int i = 0; i < minLen; i++) { // Check if still unique chars are needed @@ -200,9 +202,8 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // Find lowest possible cardinality and then generate char for (int card = 1; card < lims.keySet().size(); card++) { if (chars.containsKey(card)) { - ArrayList validChars = chars.get(card); + List validChars = chars.get(card); password.append(validChars.get(rand.nextInt(validChars.size()))); -// LOGGER.trace(password.toString()); break; } } @@ -265,9 +266,8 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // Find lowest possible cardinality and then generate char for (int card = 1; card <= lims.keySet().size(); card++) { if (chars.containsKey(card)) { - ArrayList validChars = chars.get(card); + List validChars = chars.get(card); password.append(validChars.get(rand.nextInt(validChars.size()))); -// LOGGER.trace(password.toString()); break; } } @@ -285,7 +285,7 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea // Shuffle output to solve pattern like output StrBuilder sb = new StrBuilder(password.substring(0, 1)); - ArrayList shuffleBuffer = StringPolicyUtils.stringTokenizer(password.substring(1)); + List shuffleBuffer = StringPolicyUtils.stringTokenizer(password.substring(1)); Collections.shuffle(shuffleBuffer); sb.appendAll(shuffleBuffer); @@ -299,14 +299,14 @@ public static String generate(StringPolicyType policy, int defaultLength, boolea /** * Count cardinality */ - private static HashMap> cardinalityCounter( - HashMap> lims, ArrayList password, Boolean skipMatchedLims, + private static Map> cardinalityCounter( + Map> lims, List password, Boolean skipMatchedLims, boolean uniquenessReached, OperationResult op) { HashMap counter = new HashMap(); for (StringLimitType l : lims.keySet()) { int counterKey = 1; - ArrayList chars = lims.get(l); + List chars = lims.get(l); int i = 0; if (null != password) { i = charIntersectionCounter(lims.get(l), password); @@ -358,7 +358,7 @@ private static HashMap> cardinalityCounter( } // Transpone to better format - HashMap> ret = new HashMap>(); + Map> ret = new HashMap>(); for (String s : counter.keySet()) { // if not there initialize if (null == ret.get(counter.get(s))) { @@ -369,7 +369,7 @@ private static HashMap> cardinalityCounter( return ret; } - private static int charIntersectionCounter(ArrayList a, ArrayList b) { + private static int charIntersectionCounter(List a, List b) { int ret = 0; for (String s : b) { if (a.contains(s)) { diff --git a/infra/common/src/test/java/com/evolveum/midpoint/common/test/PasswordPolicyValidatorTest.java b/infra/common/src/test/java/com/evolveum/midpoint/common/test/PasswordPolicyValidatorTest.java index 6a00f70356f..c5745fec6dd 100644 --- a/infra/common/src/test/java/com/evolveum/midpoint/common/test/PasswordPolicyValidatorTest.java +++ b/infra/common/src/test/java/com/evolveum/midpoint/common/test/PasswordPolicyValidatorTest.java @@ -253,7 +253,7 @@ public void passwordGeneratorTest(final String TEST_NAME, String policyFilename) } private void assertPassword(String passwd, ValuePolicyType pp) { - OperationResult validationResult = PasswordPolicyUtils.validatePassword(passwd, pp); + OperationResult validationResult = PasswordPolicyUtils.validatePassword(passwd, null, pp); if (!validationResult.isSuccess()) { AssertJUnit.fail(validationResult.debugDump()); } @@ -296,7 +296,7 @@ private ValuePolicyType parsePasswordPolicy(String filename) throws SchemaExcept private boolean pwdValidHelper(String password, ValuePolicyType pp) { OperationResult op = new OperationResult("Password Validator test with password:" + password); - PasswordPolicyUtils.validatePassword(password, pp, op); + PasswordPolicyUtils.validatePassword(password, null, pp, op); op.computeStatus(); String msg = "-> Policy "+pp.getName()+", password '"+password+"': "+op.getStatus(); System.out.println(msg); @@ -324,7 +324,7 @@ public void passwordValidationMultipleTest() throws Exception { pps.add(pp); pps.add(pp); - PasswordPolicyUtils.validatePassword(password, pps, op); + PasswordPolicyUtils.validatePassword(password, null, pps, op); op.computeStatus(); LOGGER.error(op.debugDump()); AssertJUnit.assertTrue(op.isSuccess()); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java index 440674079f5..eaf69bad78b 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/PasswordPolicyProcessor.java @@ -19,10 +19,6 @@ import java.util.ArrayList; import java.util.List; -import com.evolveum.midpoint.schema.util.ObjectTypeUtil; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; - import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -34,6 +30,8 @@ import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensObjectDeltaOperation; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.PrismReference; @@ -44,21 +42,27 @@ import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.delta.ReferenceDelta; +import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordHistoryEntryType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ValuePolicyType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; @Component @@ -139,11 +143,11 @@ void processPasswordPolicy(LensFocusContext focusContex passwordPolicy = focusContext.getOrgPasswordPolicy(); } - processPasswordPolicy(passwordPolicy, passwordValueProperty, result); + processPasswordPolicy(passwordPolicy, focusContext.getObjectOld(), passwordValueProperty, result); } - private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismProperty passwordProperty, OperationResult result) + private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismObject focus, PrismProperty passwordProperty, OperationResult result) throws PolicyViolationException, SchemaException { if (passwordPolicy == null) { @@ -152,8 +156,9 @@ private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismProperty } String passwordValue = determinePasswordValue(passwordProperty); - - boolean isValid = PasswordPolicyUtils.validatePassword(passwordValue, passwordPolicy, result); + List oldPasswords = determineOldPasswordValues(focus); + + boolean isValid = PasswordPolicyUtils.validatePassword(passwordValue, oldPasswords, passwordPolicy, result); if (!isValid) { result.computeStatus(); @@ -161,6 +166,32 @@ private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismProperty } } + private List determineOldPasswordValues(PrismObject focus) { + if (focus == null) { + return null; + } + List oldPasswords = null; + if (focus.getCompileTimeClass().equals(UserType.class)) { + + PrismContainer historyEntries = focus.findContainer(new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_HISTORY_ENTRY)); + if (historyEntries == null || historyEntries.isEmpty()) { + return null; + } + + List historyEntryValues = PrismContainerValue.fromPcvList(historyEntries.getValues()); + oldPasswords = new ArrayList<>(historyEntryValues.size()); + for (PasswordHistoryEntryType historyEntryValue : historyEntryValues) { + try { + oldPasswords.add(protector.decryptString(historyEntryValue.getValue())); + } catch (EncryptionException e) { //TODO: do we want to fail when we can't decrypt old values? + throw new SystemException("Failed to process password for user: " , e); + } + } + + } + return oldPasswords; + } + private boolean wasExecuted(ObjectDelta userDelta, LensFocusContext focusContext){ for (LensObjectDeltaOperation executedDeltaOperation : focusContext.getExecutedDeltas()){ @@ -199,6 +230,9 @@ protected ValuePolicyType determineV protected ValuePolicyType determineValuePolicy(ObjectDelta userDelta, Task task, OperationResult result) throws SchemaException { + if (userDelta == null) { + return null; + } ReferenceDelta orgDelta = userDelta.findReferenceModification(UserType.F_PARENT_ORG_REF); LOGGER.trace("Determining password policy from org delta."); @@ -347,7 +381,7 @@ void processPasswordPolicy(LensProjectionContext projecti passwordPolicy = projectionContext.getEffectivePasswordPolicy(); } - processPasswordPolicy(passwordPolicy, password, result); + processPasswordPolicy(passwordPolicy, null, password, result); } private boolean isCheckOrgPolicy(LensContext context) throws SchemaException{ diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicyProcessor.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicyProcessor.java index 3443b91d37d..37466ad9d63 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicyProcessor.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicyProcessor.java @@ -14,6 +14,7 @@ import org.testng.AssertJUnit; import org.testng.annotations.Test; +import com.evolveum.midpoint.model.api.PolicyViolationException; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.path.ItemPath; @@ -62,7 +63,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti repoAddObjectFromFile(PASSWORD_HISTORY_POLICY_FILE, ValuePolicyType.class, initResult); repoAddObjectFromFile(PASSWORD_NO_HISTORY_POLICY_FILE, ValuePolicyType.class, initResult); - + deleteObject(UserType.class, USER_JACK_OID); } @@ -215,6 +216,25 @@ public void test103modifyUserPasswordAgain() throws Exception { } + @Test + public void test104modifyUserPasswordSamePassword() throws Exception { + String title = "test103modifyUserPasswordAgain"; + Task task = taskManager.createTaskInstance(title); + OperationResult result = task.getResult(); + + // WHEN + ProtectedStringType newValue = new ProtectedStringType(); + newValue.setClearValue("ch4nGedP433w0rd"); + try { + modifyObjectReplaceProperty(UserType.class, USER_JACK_OID, + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE), + task, result, newValue); + fail("Expected PolicyViolationException but didn't get one."); + } catch (PolicyViolationException ex) { + // THIS IS OK + } + } + @Test public void test200initNoHistoryPasswordPolicy() throws Exception { String title = "test200initNoHistoryPasswordPolicy"; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java index a5bb3c3b744..4fd5bb7b083 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/sync/TestImportRecon.java @@ -1575,7 +1575,7 @@ public void test330ReconcileDummyAzureAddAccountRapp() throws Exception { PrismObject passwordPolicy = getObjectViaRepo(ValuePolicyType.class, PASSWORD_POLICY_LOWER_CASE_ALPHA_AZURE_OID); - OperationResult satisfyPolicyResult = PasswordPolicyUtils.validatePassword(stringPassword, passwordPolicy.asObjectable()); + OperationResult satisfyPolicyResult = PasswordPolicyUtils.validatePassword(stringPassword, null, passwordPolicy.asObjectable()); assertTrue("Password doesn't satisfy password policy, generated password: " + stringPassword, satisfyPolicyResult.isSuccess()); /////////