From 19155c62855cb6810c12458e04a088fbc218fe51 Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Tue, 21 Jun 2016 17:24:51 +0200 Subject: [PATCH 1/5] Password history schema --- .../xml/ns/public/common/common-core-3.xsd | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd index 4f45f01caab..605bff20de3 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-core-3.xsd @@ -2547,11 +2547,55 @@ - + + + + + + Contains a single entry of the password history. It contains a historical value + of a password together with essential metadata. + + + + + + + + + + Timestamps and general metadata describing the credential change. + This is a copy of the original metadata of the password before it was + changed and before it was moved to the history. + + + true + + + + + + + + The timestamp when this password was changed to a different value. + This is different from the date in metadata. The dates in metadata + tells when this password was created (when it started to be valid). + The changeTimestamp tells when the password stopped to be valid and + was moved to the history entries. This timestamp can be used to + chronologically order the entries. + + + true + true + + + + + + @@ -10159,6 +10203,15 @@ + + + + The number of entries to keep in the password history. Also specifies the + number of past passwords that will be checked before accepting a new password + change. + + + From ab69689d6830f9c4e66f8790e7f380088331170d Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 28 Jun 2016 14:06:27 +0200 Subject: [PATCH 2/5] password history impelmentation - saving old passwords.. --- .../midpoint/prism/PrismContainerValue.java | 13 + .../lens/projector/CredentialsProcessor.java | 570 ++++++++++++------ .../projector/PasswordPolicyProcessor.java | 4 +- 3 files changed, 384 insertions(+), 203 deletions(-) diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java index b84bc445a92..fed6596f738 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismContainerValue.java @@ -1582,4 +1582,17 @@ public static List> toPcvList(L } return rv; } + + public static List fromPcvList(List> values) { + if (values == null) { + return null; + } + List realValues = new ArrayList<>(values.size()); + for (PrismContainerValue value : values) { + realValues.add(value.asContainerable()); + } + + return realValues; + + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java index a94567313fe..7eaa55324a3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java @@ -15,12 +15,17 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; - +import java.util.Comparator; +import java.util.List; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.PolicyViolationException; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; @@ -33,11 +38,12 @@ import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.prism.Containerable; +import com.evolveum.midpoint.prism.ComplexTypeDefinition; import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.ItemDefinition; import com.evolveum.midpoint.prism.OriginType; import com.evolveum.midpoint.prism.PrismContainer; +import com.evolveum.midpoint.prism.PrismContainerDefinition; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; @@ -53,13 +59,9 @@ import com.evolveum.midpoint.prism.delta.PartiallyResolvedDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.path.ItemPathSegment; -import com.evolveum.midpoint.prism.schema.PrismSchema; -import com.evolveum.midpoint.prism.schema.SchemaRegistry; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.constants.ExpressionConstants; import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.processor.*; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; @@ -67,154 +69,184 @@ import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractCredentialType; +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.MappingStrengthType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +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.StringPolicyType; +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; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - /** * Processor that takes password from user and synchronizes it to accounts. *

- * The implementation is very simple now. It only cares about password value, not - * expiration or other password facets. It completely ignores other credential types. + * The implementation is very simple now. It only cares about password value, + * not expiration or other password facets. It completely ignores other + * credential types. * * @author Radovan Semancik */ @Component public class CredentialsProcessor { - private static final Trace LOGGER = TraceManager.getTrace(CredentialsProcessor.class); - - @Autowired(required = true) - private PrismContext prismContext; - - @Autowired(required = true) - private MappingFactory mappingFactory; - - @Autowired(required = true) - private MappingEvaluator mappingEvaluator; - - @Autowired(required = true) - private PasswordPolicyProcessor passwordPolicyProcessor; - - public void processFocusCredentials(LensContext context, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - processFocusPassword((LensContext)context, now, task, result); - } - } - - private void processFocusPassword(LensContext context, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - - processFocusCredentialsCommon(context, new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD), now, task, result); - - passwordPolicyProcessor.processPasswordPolicy(focusContext, context, task, result); - } - - public void processProjectionCredentials(LensContext context, LensProjectionContext projectionContext, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - processProjectionPassword((LensContext)context, projectionContext, now, task, result); - } - - passwordPolicyProcessor.processPasswordPolicy(projectionContext, context, task, result); - } - - private void processProjectionPassword(LensContext context, - final LensProjectionContext accCtx, XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - LensFocusContext focusContext = context.getFocusContext(); - ObjectDelta focusDelta = focusContext.getDelta(); - - PropertyDelta userPasswordValueDelta = null; - if (focusDelta != null) { - userPasswordValueDelta = focusDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - } - - PrismObject userNew = focusContext.getObjectNew(); - if (userNew == null) { - // This must be a user delete or something similar. No point in proceeding - LOGGER.trace("userNew is null, skipping credentials processing"); - return; - } - - PrismObjectDefinition accountDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class); - PrismPropertyDefinition accountPasswordPropertyDefinition = accountDefinition.findPropertyDefinition(SchemaConstants.PATH_PASSWORD_VALUE); - - ResourceShadowDiscriminator rat = accCtx.getResourceShadowDiscriminator(); - - ObjectDelta accountDelta = accCtx.getDelta(); - PropertyDelta accountPasswordValueDelta = null; - if (accountDelta != null) { - accountPasswordValueDelta = accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); - } - if (accountDelta != null && accountDelta.getChangeType() == ChangeType.MODIFY) { - if (accountPasswordValueDelta != null && (accountPasswordValueDelta.isAdd() || accountDelta.isDelete())) { - throw new SchemaException("Password for account "+rat+" cannot be added or deleted, it can only be replaced"); - } - } - if (accountDelta != null && (accountDelta.getChangeType() == ChangeType.ADD || accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD)) { - // adding new account, synchronize password regardless whether the password was changed or not. - } else if (userPasswordValueDelta != null) { - // user password was changed. synchronize it regardless of the account change. - } else { - LOGGER.trace("No change in password and the account is not added, skipping credentials processing for account " + rat); - return; - } - - RefinedObjectClassDefinition refinedAccountDef = accCtx.getStructuralObjectClassDefinition(); - if (refinedAccountDef == null){ - LOGGER.trace("No RefinedAccountDefinition, therefore also no password outbound definition, skipping credentials processing for account " + rat); - return; - } - - MappingType outboundMappingType = refinedAccountDef.getPasswordOutbound(); - - if (outboundMappingType == null) { - LOGGER.trace("No outbound definition in password definition in credentials in account type {}, skipping credentials processing", rat); - return; - } - - Mapping,PrismPropertyDefinition> passwordMapping = mappingFactory.createMapping(outboundMappingType, - "outbound password mapping in account type " + rat); - if (!passwordMapping.isApplicableToChannel(context.getChannel())) { - return; - } - - passwordMapping.setDefaultTargetDefinition(accountPasswordPropertyDefinition); - ItemDeltaItem,PrismPropertyDefinition> userPasswordIdi = focusContext.getObjectDeltaObject().findIdi(SchemaConstants.PATH_PASSWORD_VALUE); - Source,PrismPropertyDefinition> source = new Source<>(userPasswordIdi, ExpressionConstants.VAR_INPUT); + private static final Trace LOGGER = TraceManager.getTrace(CredentialsProcessor.class); + + @Autowired(required = true) + private PrismContext prismContext; + + @Autowired(required = true) + private MappingFactory mappingFactory; + + @Autowired(required = true) + private MappingEvaluator mappingEvaluator; + + @Autowired(required = true) + private PasswordPolicyProcessor passwordPolicyProcessor; + + public void processFocusCredentials(LensContext context, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, PolicyViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + processFocusPassword((LensContext) context, now, task, result); + } + } + + private void processFocusPassword(LensContext context, XMLGregorianCalendar now, + Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, PolicyViolationException { + LensFocusContext focusContext = context.getFocusContext(); + + processFocusCredentialsCommon(context, + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD), now, task, result); + + passwordPolicyProcessor.processPasswordPolicy(focusContext, context, task, result); + } + + public void processProjectionCredentials(LensContext context, + LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, + OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, PolicyViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + processProjectionPassword((LensContext) context, projectionContext, now, + task, result); + } + + passwordPolicyProcessor.processPasswordPolicy(projectionContext, context, task, result); + } + + private void processProjectionPassword(LensContext context, + final LensProjectionContext accCtx, XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { + LensFocusContext focusContext = context.getFocusContext(); + ObjectDelta focusDelta = focusContext.getDelta(); + + PropertyDelta userPasswordValueDelta = null; + if (focusDelta != null) { + userPasswordValueDelta = focusDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); + } + + PrismObject userNew = focusContext.getObjectNew(); + if (userNew == null) { + // This must be a user delete or something similar. No point in + // proceeding + LOGGER.trace("userNew is null, skipping credentials processing"); + return; + } + + PrismObjectDefinition accountDefinition = prismContext.getSchemaRegistry() + .findObjectDefinitionByCompileTimeClass(ShadowType.class); + PrismPropertyDefinition accountPasswordPropertyDefinition = accountDefinition + .findPropertyDefinition(SchemaConstants.PATH_PASSWORD_VALUE); + + ResourceShadowDiscriminator rat = accCtx.getResourceShadowDiscriminator(); + + ObjectDelta accountDelta = accCtx.getDelta(); + PropertyDelta accountPasswordValueDelta = null; + if (accountDelta != null) { + accountPasswordValueDelta = accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); + } + if (accountDelta != null && accountDelta.getChangeType() == ChangeType.MODIFY) { + if (accountPasswordValueDelta != null + && (accountPasswordValueDelta.isAdd() || accountDelta.isDelete())) { + throw new SchemaException("Password for account " + rat + + " cannot be added or deleted, it can only be replaced"); + } + } + if (accountDelta != null && (accountDelta.getChangeType() == ChangeType.ADD + || accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD)) { + // adding new account, synchronize password regardless whether the + // password was changed or not. + } else if (userPasswordValueDelta != null) { + // user password was changed. synchronize it regardless of the + // account change. + } else { + LOGGER.trace( + "No change in password and the account is not added, skipping credentials processing for account " + + rat); + return; + } + + RefinedObjectClassDefinition refinedAccountDef = accCtx.getStructuralObjectClassDefinition(); + if (refinedAccountDef == null) { + LOGGER.trace( + "No RefinedAccountDefinition, therefore also no password outbound definition, skipping credentials processing for account " + + rat); + return; + } + + MappingType outboundMappingType = refinedAccountDef.getPasswordOutbound(); + + if (outboundMappingType == null) { + LOGGER.trace( + "No outbound definition in password definition in credentials in account type {}, skipping credentials processing", + rat); + return; + } + + Mapping, PrismPropertyDefinition> passwordMapping = mappingFactory + .createMapping(outboundMappingType, "outbound password mapping in account type " + rat); + if (!passwordMapping.isApplicableToChannel(context.getChannel())) { + return; + } + + passwordMapping.setDefaultTargetDefinition(accountPasswordPropertyDefinition); + ItemDeltaItem, PrismPropertyDefinition> userPasswordIdi = focusContext + .getObjectDeltaObject().findIdi(SchemaConstants.PATH_PASSWORD_VALUE); + Source, PrismPropertyDefinition> source = new Source<>( + userPasswordIdi, ExpressionConstants.VAR_INPUT); passwordMapping.setDefaultSource(source); passwordMapping.setOriginType(OriginType.OUTBOUND); passwordMapping.setOriginObject(accCtx.getResource()); - + if (passwordMapping.getStrength() != MappingStrengthType.STRONG) { - if (accountPasswordValueDelta != null && !accountPasswordValueDelta.isEmpty()) { - return; - } - } - + if (accountPasswordValueDelta != null && !accountPasswordValueDelta.isEmpty()) { + return; + } + } + StringPolicyResolver stringPolicyResolver = new StringPolicyResolver() { private ItemPath outputPath; private ItemDefinition outputDefinition; + @Override public void setOutputPath(ItemPath outputPath) { this.outputPath = outputPath; } - + @Override public void setOutputDefinition(ItemDefinition outputDefinition) { this.outputDefinition = outputDefinition; } - + @Override public StringPolicyType resolve() { ValuePolicyType passwordPolicy = accCtx.getEffectivePasswordPolicy(); @@ -225,7 +257,7 @@ public StringPolicyType resolve() { } }; passwordMapping.setStringPolicyResolver(stringPolicyResolver); - + mappingEvaluator.evaluateMapping(passwordMapping, context, task, result); // TODO review all this code !! MID-3156 @@ -234,68 +266,116 @@ public StringPolicyType resolve() { PrismProperty accountPasswordNew = (PrismProperty) passwordMapping.getOutput(); if (accountPasswordNew == null || accountPasswordNew.isEmpty()) { if (passwordMapping.getOutputTriple() == null) { - LOGGER.trace("Credentials 'password' expression resulted in null output triple, skipping credentials processing for {}", rat); + LOGGER.trace( + "Credentials 'password' expression resulted in null output triple, skipping credentials processing for {}", + rat); return; } if (passwordMapping.getStrength() != MappingStrengthType.STRONG) { - LOGGER.trace("Credentials 'password' expression resulted in 'no value' via non-strong mapping, skipping credentials processing for {}", rat); + LOGGER.trace( + "Credentials 'password' expression resulted in 'no value' via non-strong mapping, skipping credentials processing for {}", + rat); return; } } - PropertyDelta accountPasswordDeltaNew = new PropertyDelta<>(SchemaConstants.PATH_PASSWORD_VALUE, accountPasswordPropertyDefinition, prismContext); + PropertyDelta accountPasswordDeltaNew = new PropertyDelta<>( + SchemaConstants.PATH_PASSWORD_VALUE, accountPasswordPropertyDefinition, prismContext); if (accountPasswordNew != null) { accountPasswordDeltaNew.setValuesToReplace(accountPasswordNew.getClonedValues()); } else { - accountPasswordDeltaNew.setValuesToReplace(Collections.>emptyList()); + accountPasswordDeltaNew + .setValuesToReplace(Collections.> emptyList()); + } + LOGGER.trace("Adding new password delta for account {}", rat); + accCtx.swallowToSecondaryDelta(accountPasswordDeltaNew); + + } + + private void processFocusCredentialsCommon(LensContext context, + ItemPath credentialsPath, XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { + + LensFocusContext focusContext = context.getFocusContext(); + + if (focusContext.isAdd()) { + PrismObject focus = focusContext.getObjectNew(); + PrismContainer credentialsContainer = focus + .findContainer(credentialsPath); + if (credentialsContainer != null) { + for (PrismContainerValue cVal : credentialsContainer.getValues()) { + processCredentialsCommonAdd(focus, context, credentialsPath, cVal, now, task, result); + } + } + } else if (focusContext.isModify()) { + ObjectDelta focusDelta = focusContext.getDelta(); + PrismObject focus = focusContext.getObjectOld(); + if (focus == null) { + focus = focusContext.getObjectCurrent(); + } + if (focus == null) { + focus = focusContext.getObjectNew(); + } + ContainerDelta containerDelta = focusDelta + .findContainerDelta(credentialsPath); + if (containerDelta != null) { + if (containerDelta.isAdd()) { + for (PrismContainerValue cVal : containerDelta.getValuesToAdd()) { + processCredentialsCommonAdd(focus, context, credentialsPath, cVal, now, task, result); + } + } + if (containerDelta.isReplace()) { + for (PrismContainerValue cVal : containerDelta + .getValuesToReplace()) { + processCredentialsCommonAdd(focus, context, credentialsPath, cVal, now, task, result); + } + } + } else { + if (hasValueDelta(focusDelta, credentialsPath)) { + Collection> metaDeltas = LensUtil.createModifyMetadataDeltas( + context, credentialsPath.subPath(AbstractCredentialType.F_METADATA), + focusContext.getObjectDefinition(), now, task); + for (ItemDelta metaDelta : metaDeltas) { + context.getFocusContext().swallowToSecondaryDelta(metaDelta); + } + + processPasswordHistoryDeltas(focus, context, now, task, result); + } + } + } + } + + private int getMaxPasswordsToSave(LensFocusContext focusContext, + LensContext context, Task task, OperationResult result) throws SchemaException { + ValuePolicyType passwordPolicy; + if (focusContext.getOrgPasswordPolicy() == null) { + passwordPolicy = passwordPolicyProcessor.determineValuePolicy(focusContext.getDelta(), + focusContext.getObjectAny(), context, task, result); + focusContext.setOrgPasswordPolicy(passwordPolicy); + } else { + passwordPolicy = focusContext.getOrgPasswordPolicy(); + } + + if (passwordPolicy == null) { + return 0; + } + + if (passwordPolicy.getLifetime() == null) { + return 0; } - LOGGER.trace("Adding new password delta for account {}", rat); - accCtx.swallowToSecondaryDelta(accountPasswordDeltaNew); - - } - - private void processFocusCredentialsCommon(LensContext context, - ItemPath credentialsPath, XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext.isAdd()) { - PrismObject focus = focusContext.getObjectNew(); - PrismContainer credentialsContainer = focus.findContainer(credentialsPath); - if (credentialsContainer != null) { - for (PrismContainerValue cVal: credentialsContainer.getValues()) { - processCredentialsCommonAdd(context, credentialsPath, cVal, now, task, result); - } - } - } else if (focusContext.isModify()) { - ObjectDelta focusDelta = focusContext.getDelta(); - ContainerDelta containerDelta = focusDelta.findContainerDelta(credentialsPath); - if (containerDelta != null) { - if (containerDelta.isAdd()) { - for (PrismContainerValue cVal: containerDelta.getValuesToAdd()) { - processCredentialsCommonAdd(context, credentialsPath, cVal, now, task, result); - } - } - if (containerDelta.isReplace()) { - for (PrismContainerValue cVal: containerDelta.getValuesToReplace()) { - processCredentialsCommonAdd(context, credentialsPath, cVal, now, task, result); - } - } - } else { - if (hasValueDelta(focusDelta, credentialsPath)) { - Collection> metaDeltas = LensUtil.createModifyMetadataDeltas(context, credentialsPath.subPath(AbstractCredentialType.F_METADATA), - focusContext.getObjectDefinition(), now, task); - for (ItemDelta metaDelta: metaDeltas) { - context.getFocusContext().swallowToSecondaryDelta(metaDelta); - } - } - } - } - } - - private boolean hasValueDelta(ObjectDelta focusDelta, ItemPath credentialsPath) { - if (focusDelta == null) { - return false; - } - for (PartiallyResolvedDelta partialDelta: focusDelta.findPartial(credentialsPath)) { + + if (passwordPolicy.getLifetime().getPasswordHistoryLength() == null) { + return 0; + } + + return passwordPolicy.getLifetime().getPasswordHistoryLength().intValue(); + } + + private boolean hasValueDelta(ObjectDelta focusDelta, ItemPath credentialsPath) { + if (focusDelta == null) { + return false; + } + for (PartiallyResolvedDelta partialDelta : focusDelta + .findPartial(credentialsPath)) { LOGGER.trace("Residual delta:\n{}", partialDelta.debugDump()); ItemPath residualPath = partialDelta.getResidualPath(); if (residualPath == null || residualPath.isEmpty()) { @@ -311,42 +391,130 @@ private boolean hasValueDelta(ObjectDelta focusDelta, I return false; } + private void processCredentialsCommonAdd(PrismObject focus, + LensContext context, ItemPath credentialsPath, + PrismContainerValue cVal, XMLGregorianCalendar now, Task task, + OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - private void processCredentialsCommonAdd(LensContext context, ItemPath credentialsPath, - PrismContainerValue cVal, XMLGregorianCalendar now, Task task, OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException { - if (hasValueChange(cVal) && !hasMetadata(cVal)) { + if (hasValueChange(cVal) && !hasMetadata(cVal)) { MetadataType metadataType = LensUtil.createCreateMetadata(context, now, task); - ContainerDelta metadataDelta = ContainerDelta.createModificationAdd(credentialsPath.subPath(AbstractCredentialType.F_METADATA), - UserType.class, prismContext, metadataType); + ContainerDelta metadataDelta = ContainerDelta.createModificationAdd( + credentialsPath.subPath(AbstractCredentialType.F_METADATA), UserType.class, prismContext, + metadataType); context.getFocusContext().swallowToSecondaryDelta(metadataDelta); + } - } + } + + private void processPasswordHistoryDeltas(PrismObject focus, + LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) + throws SchemaException { + if (focus.getCompileTimeClass().equals(UserType.class)) { + PrismContainer password = focus + .findOrCreateContainer(new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD)); + if (password == null || password.isEmpty()) { + return; + } + PrismContainer historyEntries = password + .findOrCreateContainer(PasswordType.F_HISTORY_ENTRY); + + List historyEntryValues = getSortedHistoryList(historyEntries); + createDeleteHistoryDeltasIfNeeded(historyEntryValues, context, task, result); + createAddHistoryDelta(context, password, now); + + } + + } + + private void createAddHistoryDelta(LensContext context, + PrismContainer password, XMLGregorianCalendar now) throws SchemaException { + PrismContainerValue passwordValue = password.getValue(); + PasswordType passwordRealValue = passwordValue.asContainerable(); + + PrismContainerDefinition historyEntryDefinition = password.getDefinition().findContainerDefinition(PasswordType.F_HISTORY_ENTRY); + PrismContainer historyEntry = historyEntryDefinition.instantiate(); + + PrismContainerValue hisotryEntryValue = historyEntry.createNewValue(); + + PasswordHistoryEntryType entryType = hisotryEntryValue.asContainerable(); + entryType.setValue(passwordRealValue.getValue()); + entryType.setMetadata(passwordRealValue.getMetadata()); + entryType.setChangeTimestamp(now); + + ContainerDelta addHisotryDelta = ContainerDelta + .createModificationAdd(new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_HISTORY_ENTRY), UserType.class, prismContext, entryType.clone()); + context.getFocusContext().swallowToSecondaryDelta(addHisotryDelta); + + } + + private void createDeleteHistoryDeltasIfNeeded( + List historyEntryValues, LensContext context, Task task, + OperationResult result) throws SchemaException { + + int maxPasswordsToSave = getMaxPasswordsToSave(context.getFocusContext(), context, task, result); + + int numberOfHistoryEntriesToDelete = historyEntryValues.size() - maxPasswordsToSave; + + if (numberOfHistoryEntriesToDelete >= 0) { + for (int i = 0; i < numberOfHistoryEntriesToDelete; i++) { + ContainerDelta deleteHistoryDelta = ContainerDelta + .createModificationDelete( + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, + PasswordType.F_HISTORY_ENTRY), + UserType.class, prismContext, + historyEntryValues.get(i).clone()); + context.getFocusContext().swallowToSecondaryDelta(deleteHistoryDelta); + } + } + + } + + private List getSortedHistoryList( + PrismContainer historyEntries) { + if (historyEntries.isEmpty()) { + return new ArrayList(); + } + List historyEntryValues = PrismContainerValue + .fromPcvList(historyEntries.getValues()); + + Collections.sort(historyEntryValues, new Comparator() { + + @Override + public int compare(PasswordHistoryEntryType o1, PasswordHistoryEntryType o2) { + XMLGregorianCalendar changeTimestampFirst = o1.getChangeTimestamp(); + XMLGregorianCalendar changeTimestampSecond = o2.getChangeTimestamp(); + + return changeTimestampFirst.compare(changeTimestampSecond); + } + }); + return historyEntryValues; + } private boolean hasValueChange(PrismContainerValue cVal) { - for (Item item: cVal.getItems()) { + for (Item item : cVal.getItems()) { QName itemName = item.getElementName(); if (isValueElement(itemName)) { - return true; - } + return true; + } } return false; } - + private boolean isValueElement(QName itemName) { - return !itemName.equals(AbstractCredentialType.F_FAILED_LOGINS) && - !itemName.equals(AbstractCredentialType.F_LAST_FAILED_LOGIN) && - !itemName.equals(AbstractCredentialType.F_LAST_SUCCESSFUL_LOGIN) && - !itemName.equals(AbstractCredentialType.F_METADATA) && - !itemName.equals(AbstractCredentialType.F_PREVIOUS_SUCCESSFUL_LOGIN); + return !itemName.equals(AbstractCredentialType.F_FAILED_LOGINS) + && !itemName.equals(AbstractCredentialType.F_LAST_FAILED_LOGIN) + && !itemName.equals(AbstractCredentialType.F_LAST_SUCCESSFUL_LOGIN) + && !itemName.equals(AbstractCredentialType.F_METADATA) + && !itemName.equals(AbstractCredentialType.F_PREVIOUS_SUCCESSFUL_LOGIN); } private boolean hasMetadata(PrismContainerValue cVal) { - for (Item item: cVal.getItems()) { + for (Item item : cVal.getItems()) { QName itemName = item.getElementName(); - if (itemName.equals(AbstractCredentialType.F_METADATA)) { - return true; - } + if (itemName.equals(AbstractCredentialType.F_METADATA)) { + return true; + } } return false; } 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 6c13c0ea66c..440674079f5 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 @@ -176,7 +176,7 @@ private boolean wasExecuted(ObjectDelta userDelt } //TODO: maybe some caching of orgs????? - private ValuePolicyType determineValuePolicy(ObjectDelta userDelta, PrismObject object, LensContext context, Task task, OperationResult result) throws SchemaException{ + protected ValuePolicyType determineValuePolicy(ObjectDelta userDelta, PrismObject object, LensContext context, Task task, OperationResult result) throws SchemaException{ //check the modification of organization first ValuePolicyType valuePolicy = determineValuePolicy(userDelta, task, result); @@ -197,7 +197,7 @@ private ValuePolicyType determineVa return valuePolicy; } - private ValuePolicyType determineValuePolicy(ObjectDelta userDelta, Task task, OperationResult result) + protected ValuePolicyType determineValuePolicy(ObjectDelta userDelta, Task task, OperationResult result) throws SchemaException { ReferenceDelta orgDelta = userDelta.findReferenceModification(UserType.F_PARENT_ORG_REF); From e336faa80f4d8061677a1a8c5c4adcc93cc714a8 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Mon, 11 Jul 2016 13:58:01 +0200 Subject: [PATCH 3/5] some tests for testing password history saving --- .../lens/projector/CredentialsProcessor.java | 45 ++- .../impl/lens/TestAssignmentEvaluator.java | 19 +- .../lens/TestPasswordPolicyProcessor.java | 342 ++++++++++++++++++ .../lens/password-policy-history.xml | 37 ++ .../lens/password-policy-no-history.xml | 37 ++ model/model-impl/testng-unit.xml | 1 + .../midpoint/init/ApplicationHomeSetup.java | 1 + 7 files changed, 448 insertions(+), 34 deletions(-) create mode 100644 model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicyProcessor.java create mode 100644 model/model-impl/src/test/resources/lens/password-policy-history.xml create mode 100644 model/model-impl/src/test/resources/lens/password-policy-no-history.xml diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java index 7eaa55324a3..fdb63759f86 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/CredentialsProcessor.java @@ -23,6 +23,7 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import org.apache.commons.lang.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -297,8 +298,16 @@ private void processFocusCredentialsCommon(LensContext LensFocusContext focusContext = context.getFocusContext(); + ObjectDelta focusDelta = focusContext.getDelta(); + PrismObject focus = focusContext.getObjectAny(); +// if (focus == null) { +// focus = focusContext.getObjectCurrent(); +// } +// if (focus == null) { +// focus = focusContext.getObjectNew(); +// } if (focusContext.isAdd()) { - PrismObject focus = focusContext.getObjectNew(); + focus = focusContext.getObjectAny(); PrismContainer credentialsContainer = focus .findContainer(credentialsPath); if (credentialsContainer != null) { @@ -307,14 +316,6 @@ private void processFocusCredentialsCommon(LensContext } } } else if (focusContext.isModify()) { - ObjectDelta focusDelta = focusContext.getDelta(); - PrismObject focus = focusContext.getObjectOld(); - if (focus == null) { - focus = focusContext.getObjectCurrent(); - } - if (focus == null) { - focus = focusContext.getObjectNew(); - } ContainerDelta containerDelta = focusDelta .findContainerDelta(credentialsPath); if (containerDelta != null) { @@ -338,10 +339,14 @@ private void processFocusCredentialsCommon(LensContext context.getFocusContext().swallowToSecondaryDelta(metaDelta); } - processPasswordHistoryDeltas(focus, context, now, task, result); + } } } + if (focusContext.isDelete()){ + return; + } + processPasswordHistoryDeltas(focus, context, now, task, result); } private int getMaxPasswordsToSave(LensFocusContext focusContext, @@ -410,6 +415,7 @@ private void processCredentialsCommonAdd(PrismObject fo private void processPasswordHistoryDeltas(PrismObject focus, LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + Validate.notNull(focus, "Focus object must not be null"); if (focus.getCompileTimeClass().equals(UserType.class)) { PrismContainer password = focus .findOrCreateContainer(new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD)); @@ -419,8 +425,13 @@ private void processPasswordHistoryDeltas(PrismObject f PrismContainer historyEntries = password .findOrCreateContainer(PasswordType.F_HISTORY_ENTRY); + int maxPasswordsToSave = getMaxPasswordsToSave(context.getFocusContext(), context, task, result); + List historyEntryValues = getSortedHistoryList(historyEntries); - createDeleteHistoryDeltasIfNeeded(historyEntryValues, context, task, result); + createDeleteHistoryDeltasIfNeeded(historyEntryValues, maxPasswordsToSave, context, task, result); + if (maxPasswordsToSave == 0) { + return; + } createAddHistoryDelta(context, password, now); } @@ -449,15 +460,17 @@ private void createAddHistoryDelta(LensContext context, } private void createDeleteHistoryDeltasIfNeeded( - List historyEntryValues, LensContext context, Task task, + List historyEntryValues, int maxPasswordsToSave, LensContext context, Task task, OperationResult result) throws SchemaException { - - int maxPasswordsToSave = getMaxPasswordsToSave(context.getFocusContext(), context, task, result); + + if (historyEntryValues.size() == 0) { + return; + } int numberOfHistoryEntriesToDelete = historyEntryValues.size() - maxPasswordsToSave; - + if (numberOfHistoryEntriesToDelete >= 0) { - for (int i = 0; i < numberOfHistoryEntriesToDelete; i++) { + for (int i = 0; i <= numberOfHistoryEntriesToDelete; i++) { ContainerDelta deleteHistoryDelta = ContainerDelta .createModificationDelete( new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentEvaluator.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentEvaluator.java index 5bfd2b3777c..15596b0518f 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentEvaluator.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentEvaluator.java @@ -87,24 +87,7 @@ @DirtiesContext(classMode = ClassMode.AFTER_CLASS) public class TestAssignmentEvaluator extends TestAbstractAssignmentEvaluator { -// @Autowired(required=true) -// private RepositoryService repositoryService; -// -// @Autowired(required=true) -// private ObjectResolver objectResolver; -// -// @Autowired(required=true) -// private Clock clock; -// -// @Autowired(required=true) -// private ActivationComputer activationComputer; -// -// @Autowired(required=true) -// private MappingFactory mappingFactory; -// -// @Autowired(required=true) -// private MappingEvaluator mappingEvaluator; - + protected static final File[] ROLE_CORP_FILES = { ROLE_CORP_GENERIC_METAROLE_FILE, ROLE_CORP_JOB_METAROLE_FILE, 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 new file mode 100644 index 00000000000..3443b91d37d --- /dev/null +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestPasswordPolicyProcessor.java @@ -0,0 +1,342 @@ +package com.evolveum.midpoint.model.impl.lens; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.fail; +import static com.evolveum.midpoint.test.IntegrationTestTools.display; + +import java.io.File; +import java.util.List; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; +import org.springframework.test.context.ContextConfiguration; +import org.testng.AssertJUnit; +import org.testng.annotations.Test; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +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.xml.ns._public.common.common_3.CredentialsType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +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.SystemConfigurationType; +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; + +@ContextConfiguration(locations = { "classpath:ctx-model-test-main.xml" }) +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public class TestPasswordPolicyProcessor extends AbstractLensTest { + + private static final String BASE_PATH = "src/test/resources/lens"; + + private static final String PASSWORD_HISTORY_POLICY_OID = "policy00-0000-0000-0000-000000000003"; + private static final String PASSWORD_HISTORY_POLICY_NAME = "password-policy-history.xml"; + private static final File PASSWORD_HISTORY_POLICY_FILE = new File(BASE_PATH, + PASSWORD_HISTORY_POLICY_NAME); + + private static final String PASSWORD_NO_HISTORY_POLICY_OID = "policy00-0000-0000-0000-000000000004"; + private static final String PASSWORD_NO_HISTORY_POLICY_NAME = "password-policy-no-history.xml"; + private static final File PASSWORD_NO_HISTORY_POLICY_FILE = new File(BASE_PATH, + PASSWORD_NO_HISTORY_POLICY_NAME); + + // private static final String SECURITY_POLICY_PASSWORD_POLICY_OID = + // "policy00-0000-0000-0000-000000000003"; + // private static final String SECURITY_POLICY_PASSWORD_POLICY_NAME = + // "password-policy-history.xml"; + // private static final File SECURITY_POLICY_PASSWORD_POLICY_FILE = new + // File(BASE_PATH, SECURITY_POLICY_PASSWORD_POLICY_NAME); + + private static final String OPERATION_REPLACE_PASSWORD_POLICY = "initSystemPasswordPolicy"; + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + repoAddObjectFromFile(PASSWORD_HISTORY_POLICY_FILE, ValuePolicyType.class, initResult); + repoAddObjectFromFile(PASSWORD_NO_HISTORY_POLICY_FILE, ValuePolicyType.class, initResult); + + deleteObject(UserType.class, USER_JACK_OID); + + } + + @Test + public void test000initPasswordPolicyForHistory() throws Exception { + String title = "test000initPasswordPolicyForHistory"; + initPasswordPolicy(title, PASSWORD_HISTORY_POLICY_OID); + + } + + @Test + public void test100createUserWithPassword() throws Exception { + display("test100createUserWithPassword"); + // WHEN + addObject(USER_JACK_FILE); + + // THEN + PrismObject jack = getObject(UserType.class, USER_JACK_OID); + assertNotNull("User Jack was not found.", jack); + + UserType jackType = jack.asObjectable(); + CredentialsType credentialsType = jackType.getCredentials(); + assertNotNull("No credentials set for user Jack", credentialsType); + + PasswordType passwordType = credentialsType.getPassword(); + assertNotNull("No password set for user Jack", passwordType); + + List historyEntriesType = passwordType.getHistoryEntry(); + assertEquals("Unexpected number of history entries", 1, historyEntriesType.size()); + PasswordHistoryEntryType historyEntryType = historyEntriesType.get(0); + + ProtectedStringType historyPassword = historyEntryType.getValue(); + ProtectedStringType currentPassword = passwordType.getValue(); + + assertEquals("Passwords don't match", currentPassword, historyPassword); + + } + + @Test + public void test101modifyUserPassword() throws Exception { + String title = "test100modifyUserPassword"; + display(title); + Task task = taskManager.createTaskInstance(title); + OperationResult result = task.getResult(); + + // WHEN + ProtectedStringType newValue = new ProtectedStringType(); + newValue.setClearValue("ch4nGedPa33word"); + + modifyObjectReplaceProperty(UserType.class, USER_JACK_OID, + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE), task, + result, newValue); + + // THEN + PrismObject jack = getObject(UserType.class, USER_JACK_OID); + assertNotNull("User Jack was not found.", jack); + + UserType jackType = jack.asObjectable(); + CredentialsType credentialsType = jackType.getCredentials(); + assertNotNull("No credentials set for user Jack", credentialsType); + + PasswordType passwordType = credentialsType.getPassword(); + assertNotNull("No password set for user Jack", passwordType); + ProtectedStringType passwordAfterChange = passwordType.getValue(); + assertNotNull("Password musn't be null", passwordAfterChange); + assertEquals("Password doesn't match", "ch4nGedPa33word", + protector.decryptString(passwordAfterChange)); + + List historyEntriesType = passwordType.getHistoryEntry(); + assertEquals("Unexpected number of history entries", 2, historyEntriesType.size()); + + checkHistoryEntriesValue(historyEntriesType, + new String[] { "deadmentellnotales", "ch4nGedPa33word" }); + + } + + @Test + public void test102modifyUserPassword() throws Exception { + String title = "test102removeUserPassword"; + display(title); + Task task = taskManager.createTaskInstance(title); + OperationResult result = task.getResult(); + + // WHEN + ProtectedStringType newValue = new ProtectedStringType(); + newValue.setClearValue("ch4nGedPa33w0rd"); + + modifyObjectReplaceProperty(UserType.class, USER_JACK_OID, + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE), task, + result, newValue); + + // THEN + PrismObject jack = getObject(UserType.class, USER_JACK_OID); + assertNotNull("User Jack was not found.", jack); + + UserType jackType = jack.asObjectable(); + CredentialsType credentialsType = jackType.getCredentials(); + assertNotNull("No credentials set for user Jack", credentialsType); + + PasswordType passwordType = credentialsType.getPassword(); + assertNotNull("No password set for user Jack", passwordType); + ProtectedStringType passwordAfterChange = passwordType.getValue(); + assertNotNull("Password musn't be null", passwordAfterChange); + assertEquals("Password doesn't match", "ch4nGedPa33w0rd", + protector.decryptString(passwordAfterChange)); + + List historyEntriesType = passwordType.getHistoryEntry(); + assertEquals("Unexpected number of history entries", 3, historyEntriesType.size()); + + checkHistoryEntriesValue(historyEntriesType, + new String[] { "deadmentellnotales", "ch4nGedPa33w0rd", "ch4nGedPa33word" }); + + } + + @Test + public void test103modifyUserPasswordAgain() throws Exception { + String title = "test103modifyUserPasswordAgain"; + Task task = taskManager.createTaskInstance(title); + OperationResult result = task.getResult(); + + // WHEN + ProtectedStringType newValue = new ProtectedStringType(); + newValue.setClearValue("ch4nGedP433w0rd"); + modifyObjectReplaceProperty(UserType.class, USER_JACK_OID, + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE), task, + result, newValue); + + // THEN + PrismObject jackAfterSecondChange = getObject(UserType.class, USER_JACK_OID); + assertNotNull("User Jack was not found.", jackAfterSecondChange); + + UserType jackTypeAfterSecondChange = jackAfterSecondChange.asObjectable(); + CredentialsType credentialsTypeAfterSecondChange = jackTypeAfterSecondChange.getCredentials(); + assertNotNull("No credentials set for user Jack", credentialsTypeAfterSecondChange); + + PasswordType passwordTypeAfterSecondChnage = credentialsTypeAfterSecondChange.getPassword(); + assertNotNull("No password set for user Jack", passwordTypeAfterSecondChnage); + ProtectedStringType passwordAfterSecondChange = passwordTypeAfterSecondChnage.getValue(); + assertNotNull("Password musn't be null", passwordAfterSecondChange); + assertEquals("Password doesn't match", "ch4nGedP433w0rd", + protector.decryptString(passwordAfterSecondChange)); + + List historyEntriesTypeAfterSecondChange = passwordTypeAfterSecondChnage + .getHistoryEntry(); + assertEquals("Unexpected number of history entries", 3, historyEntriesTypeAfterSecondChange.size()); + + checkHistoryEntriesValue(historyEntriesTypeAfterSecondChange, + new String[] { "ch4nGedP433w0rd", "ch4nGedPa33w0rd", "ch4nGedPa33word" }); + + } + + @Test + public void test200initNoHistoryPasswordPolicy() throws Exception { + String title = "test200initNoHistoryPasswordPolicy"; + initPasswordPolicy(title, PASSWORD_NO_HISTORY_POLICY_OID); + } + + @Test + public void test201deleteUserJack() throws Exception { + String title = "test201deleteUserJack"; + display(title); + + // WHEN + deleteObject(UserType.class, USER_JACK_OID); + + try { + getObject(UserType.class, USER_JACK_OID); + fail("Unexpected user Jack, should be deleted."); + } catch (ObjectNotFoundException ex) { + // this is OK; + } + + } + + @Test + public void test202createUserJackNoPasswordHisotry() throws Exception { + String title = "test201createUserJackNoPasswordHisotry"; + display(title); + + // WHEN + addObject(USER_JACK_FILE); + + // THEN + PrismObject userJack = getObject(UserType.class, USER_JACK_OID); + assertNotNull("Expected to find user Jack, but no one exists here", userJack); + + UserType userJackType = userJack.asObjectable(); + CredentialsType credentials = userJackType.getCredentials(); + assertNotNull("User Jack has no credentials", credentials); + + PasswordType password = credentials.getPassword(); + assertNotNull("User Jack has no password", password); + + List historyEntries = password.getHistoryEntry(); + assertEquals("Expected no history entries, but found: " + historyEntries.size(), 0, + historyEntries.size()); + + } + + @Test + public void test203modifyUserJackPasswordNoPasswordHisotry() throws Exception { + String title = "test202modifyUserJackPasswordNoPasswordHisotry"; + display(title); + Task task = taskManager.createTaskInstance(title); + OperationResult result = task.getResult(); + + // WHEN + ProtectedStringType newValue = new ProtectedStringType(); + newValue.setClearValue("n0Hist0ryEntr7"); + + modifyObjectReplaceProperty(UserType.class, USER_JACK_OID, + new ItemPath(UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_VALUE), task, + result, newValue); + + // THEN + PrismObject userJack = getObject(UserType.class, USER_JACK_OID); + assertNotNull("Expected to find user Jack, but no one exists here", userJack); + + UserType userJackType = userJack.asObjectable(); + CredentialsType credentials = userJackType.getCredentials(); + assertNotNull("User Jack has no credentials", credentials); + + PasswordType password = credentials.getPassword(); + assertNotNull("User Jack has no password", password); + + List historyEntries = password.getHistoryEntry(); + assertEquals("Expected no history entries, but found: " + historyEntries.size(), 0, + historyEntries.size()); + + } + + private void checkHistoryEntriesValue(List historyEntriesType, + String[] changedPasswords) { + for (PasswordHistoryEntryType historyEntry : historyEntriesType) { + boolean found = false; + try { + String clearValue = protector.decryptString(historyEntry.getValue()); + for (String changedPassword : changedPasswords) { + if (changedPassword.equals(clearValue)) { + found = true; + } + } + + if (!found) { + fail("Unexpected value saved in between password hisotry entries: " + clearValue); + } + } catch (EncryptionException e) { + AssertJUnit.fail("Could not encrypt password"); + } + + } + } + + private void initPasswordPolicy(String title, String passwordPolicyOid) throws Exception { + display(title); + Task task = createTask(title); + OperationResult result = task.getResult(); + + ObjectReferenceType passwordPolicyRef = ObjectTypeUtil.createObjectRef(passwordPolicyOid, + ObjectTypes.PASSWORD_POLICY); + modifyObjectReplaceReference(SystemConfigurationType.class, SYSTEM_CONFIGURATION_OID, + SystemConfigurationType.F_GLOBAL_PASSWORD_POLICY_REF, task, result, + passwordPolicyRef.asReferenceValue()); + + PrismObject systemConfiguration = getObject(SystemConfigurationType.class, + SYSTEM_CONFIGURATION_OID); + assertNotNull("System configuration cannot be null", systemConfiguration); + + SystemConfigurationType systemConfigurationType = systemConfiguration.asObjectable(); + ObjectReferenceType globalPasswordPolicy = systemConfigurationType.getGlobalPasswordPolicyRef(); + assertNotNull("Expected that global password policy is configured", globalPasswordPolicy); + assertEquals("Password policies don't match", passwordPolicyOid, globalPasswordPolicy.getOid()); + + } + +} diff --git a/model/model-impl/src/test/resources/lens/password-policy-history.xml b/model/model-impl/src/test/resources/lens/password-policy-history.xml new file mode 100644 index 00000000000..84df23531d7 --- /dev/null +++ b/model/model-impl/src/test/resources/lens/password-policy-history.xml @@ -0,0 +1,37 @@ + + + + + Testing Password History Policy + Testing password history policy + + 999 + 9 + 0 + 0 + 3 + + + Testing string policy + + 0 + 20 + + + + \ No newline at end of file diff --git a/model/model-impl/src/test/resources/lens/password-policy-no-history.xml b/model/model-impl/src/test/resources/lens/password-policy-no-history.xml new file mode 100644 index 00000000000..fa31d3910a0 --- /dev/null +++ b/model/model-impl/src/test/resources/lens/password-policy-no-history.xml @@ -0,0 +1,37 @@ + + + + + Testing Password History Policy - No History + Testing password history policy + + 999 + 9 + 0 + 0 + 0 + + + Testing string policy + + 0 + 20 + + + + \ No newline at end of file diff --git a/model/model-impl/testng-unit.xml b/model/model-impl/testng-unit.xml index 8e92e1ee444..ffdecc01726 100644 --- a/model/model-impl/testng-unit.xml +++ b/model/model-impl/testng-unit.xml @@ -67,6 +67,7 @@ + diff --git a/repo/system-init/src/main/java/com/evolveum/midpoint/init/ApplicationHomeSetup.java b/repo/system-init/src/main/java/com/evolveum/midpoint/init/ApplicationHomeSetup.java index 113d14d95ad..a051a2d6405 100644 --- a/repo/system-init/src/main/java/com/evolveum/midpoint/init/ApplicationHomeSetup.java +++ b/repo/system-init/src/main/java/com/evolveum/midpoint/init/ApplicationHomeSetup.java @@ -80,6 +80,7 @@ private void setupMidpointHomeDirectory(String midpointHomePath) { } catch (URISyntaxException | IOException e) { LOGGER.error("Error copying the content of initial-midpoint-home to {}: {}", midpointHomePath, e.getMessage(), e); } + } From ae10a84958c894e486177cbb62624b1c6c4de27c Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 12 Jul 2016 11:09:07 +0200 Subject: [PATCH 4/5] more tests for password policy - hisotry.. validating also old passwords while checking password policies --- .../common/policy/PasswordPolicyUtils.java | 431 ++++++++++-------- .../common/policy/StringPolicyUtils.java | 8 +- .../common/policy/ValuePolicyGenerator.java | 30 +- .../test/PasswordPolicyValidatorTest.java | 6 +- .../projector/PasswordPolicyProcessor.java | 52 ++- .../lens/TestPasswordPolicyProcessor.java | 22 +- .../model/intest/sync/TestImportRecon.java | 2 +- 7 files changed, 334 insertions(+), 217 deletions(-) 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()); ///////// From c3a7e2a2ab3c4ba841a4a772881acd44ba768acc Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Tue, 12 Jul 2016 12:35:52 +0200 Subject: [PATCH 5/5] fixing GUI tests.. --- .../midpoint/web/component/prism/ObjectWrapperFactory.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapperFactory.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapperFactory.java index 14592697986..9cb03f32f0f 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapperFactory.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/prism/ObjectWrapperFactory.java @@ -248,7 +248,10 @@ private void addContainerWrapper if (ApprovalSchemaType.COMPLEX_TYPE.equals(def.getTypeName())) { continue; } - + if (PasswordHistoryEntryType.COMPLEX_TYPE.equals(def.getTypeName())) { + continue; + } + LOGGER.trace("ObjectWrapper.createContainerWrapper processing definition: {}", def); PrismContainerDefinition containerDef = (PrismContainerDefinition) def;