From a0d945ddad0aaf077d55f39b2efa034c619c0c9d Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Thu, 16 Mar 2017 11:20:28 +0100 Subject: [PATCH] Shadow lifecycle - work in progress --- .../midpoint/prism/crypto/ProtectedData.java | 12 +- .../ns/_public/types_3/ProtectedDataType.java | 5 + .../schema/constants/SchemaConstants.java | 1 + .../midpoint/schema/util/FocusTypeUtil.java | 19 ++ .../xml/ns/public/common/common-core-3.xsd | 46 +++ .../model/api/expr/MidpointFunctions.java | 6 + .../script/jsr223/Jsr223ScriptEvaluator.java | 14 +- .../impl/expr/MidpointFunctionsImpl.java | 33 +++ .../midpoint/model/impl/lens/LensUtil.java | 22 +- .../lens/projector/ActivationProcessor.java | 275 +++++++++++------- .../projector/PasswordPolicyProcessor.java | 27 +- .../model/impl/lens/projector/Projector.java | 17 +- .../intest/TestModelServiceContract.java | 2 +- .../intest/password/AbstractPasswordTest.java | 166 +++++++++-- .../intest/password/TestPasswordDefault.java | 7 + .../password/TestPasswordDefaultHashing.java | 14 +- .../intest/password/TestPasswordNone.java | 12 + .../common/password-policy-global.xml | 4 +- .../test/AbstractIntegrationTest.java | 17 ++ 19 files changed, 541 insertions(+), 158 deletions(-) diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java index 2e40d9470e9..c2de3b4e22e 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/crypto/ProtectedData.java @@ -24,15 +24,17 @@ */ public interface ProtectedData { - abstract byte[] getClearBytes(); + byte[] getClearBytes(); - abstract void setClearBytes(byte[] bytes); + void setClearBytes(byte[] bytes); - abstract T getClearValue(); + T getClearValue(); - abstract void setClearValue(T data); + void setClearValue(T data); - abstract void destroyCleartext(); + void destroyCleartext(); + + boolean canGetCleartext(); EncryptedDataType getEncryptedDataType(); diff --git a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java index b1d62f231f8..6f2375d7514 100644 --- a/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java +++ b/infra/prism/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/ProtectedDataType.java @@ -166,6 +166,11 @@ public T getClearValue() { public void setClearValue(T clearValue) { this.clearValue = clearValue; } + + @Override + public boolean canGetCleartext() { + return clearValue != null || encryptedDataType != null; + } @Override public void destroyCleartext() { diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java index 13355c6e1f4..1cceeda2a63 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/constants/SchemaConstants.java @@ -216,6 +216,7 @@ public abstract class SchemaConstants { public static final ItemPath PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS = new ItemPath( UserType.F_CREDENTIALS, CredentialsType.F_PASSWORD, PasswordType.F_FAILED_LOGINS); public static final ItemPath PATH_LINK_REF = new ItemPath(FocusType.F_LINK_REF); + public static final ItemPath PATH_LIFECYCLE_STATE = new ItemPath(ObjectType.F_LIFECYCLE_STATE); public static final String NS_PROVISIONING = NS_MIDPOINT_PUBLIC + "/provisioning"; public static final String NS_PROVISIONING_LIVE_SYNC = NS_PROVISIONING + "/liveSync-3"; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java index ae5171c3f4d..06d1703f9ce 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/FocusTypeUtil.java @@ -23,11 +23,15 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentSelectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConstructionType; +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.OrderConstraintsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType; import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; +import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; /** * @author semancik @@ -147,4 +151,19 @@ public static ShadowKindType determineConstructionKind(AssignmentType assignment throw new IllegalArgumentException("Construction not defined in the assigment."); } + + public static ProtectedStringType getPasswordValue(UserType user) { + if (user == null) { + return null; + } + CredentialsType creds = user.getCredentials(); + if (creds == null) { + return null; + } + PasswordType passwd = creds.getPassword(); + if (passwd == null) { + return null; + } + return passwd.getValue(); + } } 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 30ffdd89e99..e76ab8acc05 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 @@ -4183,6 +4183,18 @@ + + + + Definition of resource object lifecycle handling. It defines + how the initial lifecycle state is determined, constraints for lifecycle + transitions, etc. + + + 3.6 + + + @@ -5078,6 +5090,39 @@ + + + + + Definition of resource object lifecycle handling. It defines + how the initial lifecycle state is determined, constraints for lifecycle + transitions, etc. + + + 3.6 + + + + + + + Lifecycle state mapping. This mapping influences how shadow lifecycle state and focus + lifecycle state are mapped. + Default outbound lifecycleState mapping is somehow smart algorithm that will consider + presence of the focus password, resource password capability, operation and so on. + It is defined to work well with hashed passwords. + Default inbound lifecycle mapping is empty: no mapping at all. In midPoint 3.6 the inbound + lifecycle mapping is not implemented. + + + + + + @@ -12621,6 +12666,7 @@ + diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java index 42f349d0fc2..afe49135f82 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/expr/MidpointFunctions.java @@ -1037,4 +1037,10 @@ List getMembers(String orgOid) List getMembersAsReferences(String orgOid) throws SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ObjectNotFoundException; + + /** + * Default function used to compute projection lifecycle. It is provided here so it can be explicitly + * invoked from a custom expression and then the result can be changed for special cases. + */ + String computeProjectionLifecycle(F focus, ShadowType shadow, ResourceType resource); } diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java index 0e3fc6867a7..2bdd7ab8a5e 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/expression/script/jsr223/Jsr223ScriptEvaluator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2013 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,8 +104,8 @@ public List evaluate(ScriptExpressionEvaluatorType try { InternalMonitor.recordScriptExecution(); evalRawResult = compiledScript.eval(bindings); - } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + } catch (Throwable e) { + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } if (outputDefinition == null) { @@ -172,8 +172,8 @@ public Object evaluateReportScript(String codeString, ExpressionVariables va try { InternalMonitor.recordScriptExecution(); evalRawResult = compiledScript.eval(bindings); - } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + } catch (Throwable e) { + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } @@ -190,7 +190,7 @@ private CompiledScript createCompiledScript(String codeString, String contextDes InternalMonitor.recordScriptCompile(); compiledScript = ((Compilable)scriptEngine).compile(codeString); } catch (ScriptException e) { - throw new ExpressionEvaluationException(e.getMessage() + " " + contextDescription, e); + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } scriptCache.put(codeString, compiledScript); return compiledScript; @@ -201,7 +201,7 @@ private T convertScalarResult(Class expectedType, Object rawValue, String T convertedValue = ExpressionUtil.convertValue(expectedType, rawValue, protector, prismContext); return convertedValue; } catch (IllegalArgumentException e) { - throw new ExpressionEvaluationException(e.getMessage()+" in "+contextDescription, e); + throw new ExpressionEvaluationException(e.getMessage() + " in " + contextDescription, e); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java index 248ab2c38fa..705a4603620 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/expr/MidpointFunctionsImpl.java @@ -44,6 +44,7 @@ import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.ResultHandler; import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.*; import com.evolveum.midpoint.task.api.Task; @@ -60,6 +61,9 @@ 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.resource.capabilities_3.ActivationCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PasswordCapabilityType; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; @@ -1241,4 +1245,33 @@ public List getMembers(String orgOid) throws SchemaException, ObjectNo .build(); return searchObjects(UserType.class, query, null); } + + @Override + public String computeProjectionLifecycle(F focus, ShadowType shadow, ResourceType resource) { + if (focus == null || shadow == null) { + return null; + } + if (!(focus instanceof UserType)) { + return null; + } + if (shadow.getKind() != null && shadow.getKind() != ShadowKindType.ACCOUNT) { + return null; + } + ProtectedStringType passwordPs = FocusTypeUtil.getPasswordValue((UserType)focus); + if (passwordPs != null && passwordPs.canGetCleartext()) { + return null; + } + CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(resource, CredentialsCapabilityType.class); + if (credentialsCapabilityType == null) { + return null; + } + PasswordCapabilityType passwordCapabilityType = credentialsCapabilityType.getPassword(); + if (passwordCapabilityType == null) { + return null; + } + if (passwordCapabilityType.isEnabled() == Boolean.FALSE) { + return null; + } + return SchemaConstants.LIFECYCLE_PROPOSED; + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 73496f6a50f..c9798441d4e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -1205,16 +1205,34 @@ public static void processRuleWithException(EvaluatedPolicyRule rule, EvaluatedP } + public static void partialExecute(String componentName, ProjectorComponentRunnable runnable, Supplier optionSupplier) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException { + partialExecute(componentName, runnable, optionSupplier, null); + } + + public static void partialExecute(String componentName, ProjectorComponentRunnable runnable, + Supplier optionSupplier, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException { PartialProcessingTypeType option = optionSupplier.get(); if (option == PartialProcessingTypeType.SKIP) { LOGGER.debug("Skipping projector component {} because partial execution option is set to {}", componentName, option); } else { LOGGER.trace("Projector component started: {}", componentName); - runnable.run(); - LOGGER.trace("Projector component finished: {}", componentName); + try { + runnable.run(); + LOGGER.trace("Projector component finished: {}", componentName); + } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException + | PolicyViolationException | ExpressionEvaluationException | ObjectAlreadyExistsException | RuntimeException | Error e) { + LOGGER.trace("Projector component error: {}: {}: {}", componentName, e.getClass().getSimpleName(), e.getMessage()); + if (result != null) { + result.recordFatalError(e); + } + throw e; + } + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index 33d875c3d31..e52d59babaa 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2016 Evolveum + * Copyright (c) 2010-2017 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,9 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; +import com.evolveum.midpoint.common.refinery.CompositeRefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.api.expr.MidpointFunctions; import com.evolveum.midpoint.model.common.expression.ExpressionUtil; import com.evolveum.midpoint.model.common.expression.ItemDeltaItem; import com.evolveum.midpoint.model.common.expression.Source; @@ -50,6 +52,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceActivationDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceBidirectionalMappingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectLifecycleDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceObjectTypeDefinitionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; @@ -92,6 +95,9 @@ public class ActivationProcessor { @Autowired private MappingEvaluator mappingHelper; + + @Autowired + private MidpointFunctions midpointFunctions; public void processActivation(LensContext context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { @@ -117,12 +123,12 @@ private void processActivationFocal(LensContext context processActivationUserFuture(context, projectionContext, now, task, result); } - public void processActivationUserCurrent(LensContext context, LensProjectionContext accCtx, + public void processActivationUserCurrent(LensContext context, LensProjectionContext projCtx, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - String accCtxDesc = accCtx.toHumanReadableString(); - SynchronizationPolicyDecision decision = accCtx.getSynchronizationPolicyDecision(); - SynchronizationIntent synchronizationIntent = accCtx.getSynchronizationIntent(); + String accCtxDesc = projCtx.toHumanReadableString(); + SynchronizationPolicyDecision decision = projCtx.getSynchronizationPolicyDecision(); + SynchronizationIntent synchronizationIntent = projCtx.getSynchronizationIntent(); if (decision == SynchronizationPolicyDecision.BROKEN) { LOGGER.trace("Broken projection {}, skipping further activation processing", accCtxDesc); @@ -132,31 +138,31 @@ public void processActivationUserCurrent(LensContext co throw new IllegalStateException("Decision "+decision+" already present for projection "+accCtxDesc); } - if (accCtx.isThombstone() || synchronizationIntent == SynchronizationIntent.UNLINK) { - accCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); + if (projCtx.isThombstone() || synchronizationIntent == SynchronizationIntent.UNLINK) { + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.UNLINK); LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", accCtxDesc, SynchronizationPolicyDecision.UNLINK); return; } - if (synchronizationIntent == SynchronizationIntent.DELETE || accCtx.isDelete()) { + if (synchronizationIntent == SynchronizationIntent.DELETE || projCtx.isDelete()) { // TODO: is this OK? - accCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); + projCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.DELETE); LOGGER.trace("Evaluated decision for {} to {}, skipping further activation processing", accCtxDesc, SynchronizationPolicyDecision.DELETE); return; } - boolean shadowShouldExist = evaluateExistenceMapping(context, accCtx, now, true, task, result); + boolean shadowShouldExist = evaluateExistenceMapping(context, projCtx, now, true, task, result); LOGGER.trace("Evaluated intended existence of projection {} to {}", accCtxDesc, shadowShouldExist); // Let's reconcile the existence intent (shadowShouldExist) and the synchronization intent in the context - LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, accCtx); + LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context, projCtx); if (synchronizationIntent == null || synchronizationIntent == SynchronizationIntent.SYNCHRONIZE) { if (shadowShouldExist) { - accCtx.setActive(true); - if (accCtx.isExists()) { + projCtx.setActive(true); + if (projCtx.isExists()) { if (lowerOrderContext != null && lowerOrderContext.isDelete()) { // HACK HACK HACK decision = SynchronizationPolicyDecision.DELETE; @@ -180,21 +186,21 @@ public void processActivationUserCurrent(LensContext co } } else { // Delete - if (accCtx.isExists()) { + if (projCtx.isExists()) { decision = SynchronizationPolicyDecision.DELETE; } else { // we should delete the entire context, but then we will lost track of what // happened. So just ignore it. decision = SynchronizationPolicyDecision.IGNORE; // if there are any triggers then move them to focus. We may still need them. - LensUtil.moveTriggers(accCtx, context.getFocusContext()); + LensUtil.moveTriggers(projCtx, context.getFocusContext()); } } } else if (synchronizationIntent == SynchronizationIntent.ADD) { if (shadowShouldExist) { - accCtx.setActive(true); - if (accCtx.isExists()) { + projCtx.setActive(true); + if (projCtx.isExists()) { // Attempt to add something that is already there, but should be OK decision = SynchronizationPolicyDecision.KEEP; } else { @@ -206,8 +212,8 @@ public void processActivationUserCurrent(LensContext co } else if (synchronizationIntent == SynchronizationIntent.KEEP) { if (shadowShouldExist) { - accCtx.setActive(true); - if (accCtx.isExists()) { + projCtx.setActive(true); + if (projCtx.isExists()) { decision = SynchronizationPolicyDecision.KEEP; } else { decision = SynchronizationPolicyDecision.ADD; @@ -222,7 +228,7 @@ public void processActivationUserCurrent(LensContext co LOGGER.trace("Evaluated decision for projection {} to {}", accCtxDesc, decision); - accCtx.setSynchronizationPolicyDecision(decision); + projCtx.setSynchronizationPolicyDecision(decision); PrismObject focusNew = context.getFocusContext().getObjectNew(); if (focusNew == null) { @@ -236,7 +242,7 @@ public void processActivationUserCurrent(LensContext co return; } - ResourceObjectTypeDefinitionType resourceAccountDefType = accCtx.getResourceObjectTypeDefinitionType(); + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); if (resourceAccountDefType == null) { LOGGER.trace("No refined object definition, therefore also no activation outbound definition, skipping activation processing for account " + accCtxDesc); return; @@ -247,9 +253,9 @@ public void processActivationUserCurrent(LensContext co return; } - ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(accCtx.getResource(), ActivationCapabilityType.class); + ActivationCapabilityType capActivation = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), ActivationCapabilityType.class); if (capActivation == null) { - LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", accCtx.getResource()); + LOGGER.trace("Skipping activation status and validity processing because {} has no activation capability", projCtx.getResource()); return; } @@ -259,43 +265,43 @@ public void processActivationUserCurrent(LensContext co ActivationLockoutStatusCapabilityType capLockoutStatus = CapabilityUtil.getEffectiveActivationLockoutStatus(capActivation); if (capStatus != null) { - evaluateActivationMapping(context, accCtx, + evaluateActivationMapping(context, projCtx, activationType.getAdministrativeStatus(), SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, capActivation, now, true, ActivationType.F_ADMINISTRATIVE_STATUS.getLocalPart(), task, result); } else { - LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", accCtx.getResource()); + LOGGER.trace("Skipping activation administrative status processing because {} does not have activation administrative status capability", projCtx.getResource()); } ResourceBidirectionalMappingType validFromMappingType = activationType.getValidFrom(); if (validFromMappingType == null || validFromMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", accCtx.getResource()); + LOGGER.trace("Skipping activation validFrom processing because {} does not have appropriate outbound mapping", projCtx.getResource()); } else if (capValidFrom == null && !ExpressionUtil.hasExplicitTarget(validFromMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", accCtx.getResource()); + LOGGER.trace("Skipping activation validFrom processing because {} does not have activation validFrom capability nor outbound mapping with explicit target", projCtx.getResource()); } else { - evaluateActivationMapping(context, accCtx, activationType.getValidFrom(), + evaluateActivationMapping(context, projCtx, activationType.getValidFrom(), SchemaConstants.PATH_ACTIVATION_VALID_FROM, SchemaConstants.PATH_ACTIVATION_VALID_FROM, null, now, true, ActivationType.F_VALID_FROM.getLocalPart(), task, result); } ResourceBidirectionalMappingType validToMappingType = activationType.getValidTo(); if (validToMappingType == null || validToMappingType.getOutbound() == null) { - LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", accCtx.getResource()); + LOGGER.trace("Skipping activation validTo processing because {} does not have appropriate outbound mapping", projCtx.getResource()); } else if (capValidTo == null && !ExpressionUtil.hasExplicitTarget(validToMappingType.getOutbound())) { - LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", accCtx.getResource()); + LOGGER.trace("Skipping activation validTo processing because {} does not have activation validTo capability nor outbound mapping with explicit target", projCtx.getResource()); } else { - evaluateActivationMapping(context, accCtx, activationType.getValidTo(), + evaluateActivationMapping(context, projCtx, activationType.getValidTo(), SchemaConstants.PATH_ACTIVATION_VALID_TO, SchemaConstants.PATH_ACTIVATION_VALID_TO, null, now, true, ActivationType.F_VALID_TO.getLocalPart(), task, result); } if (capLockoutStatus != null) { - evaluateActivationMapping(context, accCtx, + evaluateActivationMapping(context, projCtx, activationType.getLockoutStatus(), SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, capActivation, now, true, ActivationType.F_LOCKOUT_STATUS.getLocalPart(), task, result); } else { - LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", accCtx.getResource()); + LOGGER.trace("Skipping activation lockout status processing because {} does not have activation lockout status capability", projCtx.getResource()); } } @@ -448,85 +454,75 @@ private boolean evaluateExistenceMapping(final LensContext // "default mapping" return legal; } - - MappingInitializer,PrismPropertyDefinition> initializer = new MappingInitializer,PrismPropertyDefinition>() { - @Override - public Mapping.Builder,PrismPropertyDefinition> initialize(Mapping.Builder,PrismPropertyDefinition> builder) throws SchemaException { - // Source: legal - ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(accCtx); - Source,PrismPropertyDefinition> legalSource - = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL); - builder.setDefaultSource(legalSource); - - // Source: assigned - ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(accCtx); - Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED); - builder.addSource(assignedSource); - - // Source: focusExists - ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); - Source,PrismPropertyDefinition> focusExistsSource - = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS); - builder.addSource(focusExistsSource); - - // Variable: focus - builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject()); - - // Variable: user (for convenience, same as "focus") - builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject()); - - // Variable: shadow - builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, accCtx.getObjectDeltaObject()); - - // Variable: resource - builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, accCtx.getResource()); - - builder.setOriginType(OriginType.OUTBOUND); - builder.setOriginObject(accCtx.getResource()); - return builder; - } - }; - - final MutableBoolean output = new MutableBoolean(false); - - MappingOutputProcessor> processor = new MappingOutputProcessor>() { - @Override - public void process(ItemPath mappingOutputPath, - PrismValueDeltaSetTriple> outputTriple) throws ExpressionEvaluationException { - if (outputTriple == null) { - // The "default existence mapping" - output.setValue(legal); - return; - } - - Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); - - // MID-3507: this is probably the bug. The processor is executed after every mapping. - // The processing will die on the error if one mapping returns a value and the other mapping returns null - // (e.g. because the condition is false). This should be fixed. - if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { - throw new ExpressionEvaluationException("Activation existence expression resulted in null or empty value for projection " + accCtxDesc); - } - if (nonNegativeValues.size() > 1) { - throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + accCtxDesc); - } - - output.setValue(nonNegativeValues.iterator().next().getValue()); - } - }; - + MappingEvaluatorParams, PrismPropertyDefinition, ShadowType, F> params = new MappingEvaluatorParams<>(); params.setMappingTypes(outbound); params.setMappingDesc("outbound existence mapping in projection " + accCtxDesc); params.setNow(now); - params.setInitializer(initializer); - params.setProcessor(processor); params.setAPrioriTargetObject(accCtx.getObjectOld()); params.setEvaluateCurrent(current); params.setTargetContext(accCtx); params.setFixTarget(true); params.setContext(context); + params.setInitializer(builder -> { + // Source: legal + ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(accCtx); + Source,PrismPropertyDefinition> legalSource + = new Source<>(legalSourceIdi, ExpressionConstants.VAR_LEGAL); + builder.defaultSource(legalSource); + + // Source: assigned + ItemDeltaItem,PrismPropertyDefinition> assignedIdi = getAssignedIdi(accCtx); + Source,PrismPropertyDefinition> assignedSource = new Source<>(assignedIdi, ExpressionConstants.VAR_ASSIGNED); + builder.addSource(assignedSource); + + // Source: focusExists + ItemDeltaItem,PrismPropertyDefinition> focusExistsSourceIdi = getFocusExistsIdi(context.getFocusContext()); + Source,PrismPropertyDefinition> focusExistsSource + = new Source<>(focusExistsSourceIdi, ExpressionConstants.VAR_FOCUS_EXISTS); + builder.addSource(focusExistsSource); + + // Variable: focus + builder.addVariableDefinition(ExpressionConstants.VAR_FOCUS, context.getFocusContext().getObjectDeltaObject()); + + // Variable: user (for convenience, same as "focus") + builder.addVariableDefinition(ExpressionConstants.VAR_USER, context.getFocusContext().getObjectDeltaObject()); + + // Variable: shadow + builder.addVariableDefinition(ExpressionConstants.VAR_SHADOW, accCtx.getObjectDeltaObject()); + + // Variable: resource + builder.addVariableDefinition(ExpressionConstants.VAR_RESOURCE, accCtx.getResource()); + + builder.originType(OriginType.OUTBOUND); + builder.originObject(accCtx.getResource()); + return builder; + }); + + final MutableBoolean output = new MutableBoolean(false); + params.setProcessor((mappingOutputPath,outputTriple) -> { + if (outputTriple == null) { + // The "default existence mapping" + output.setValue(legal); + return; + } + + Collection> nonNegativeValues = outputTriple.getNonNegativeValues(); + + // MID-3507: this is probably the bug. The processor is executed after every mapping. + // The processing will die on the error if one mapping returns a value and the other mapping returns null + // (e.g. because the condition is false). This should be fixed. + if (nonNegativeValues == null || nonNegativeValues.isEmpty()) { + throw new ExpressionEvaluationException("Activation existence expression resulted in null or empty value for projection " + accCtxDesc); + } + if (nonNegativeValues.size() > 1) { + throw new ExpressionEvaluationException("Activation existence expression resulted in too many values ("+nonNegativeValues.size()+") for projection " + accCtxDesc); + } + + output.setValue(nonNegativeValues.iterator().next().getValue()); + }); + PrismPropertyDefinitionImpl shadowExistsDef = new PrismPropertyDefinitionImpl<>( SHADOW_EXISTS_PROPERTY_NAME, DOMUtil.XSD_BOOLEAN, prismContext); @@ -534,10 +530,6 @@ public void process(ItemPath mappingOutputPath, shadowExistsDef.setMaxOccurs(1); params.setTargetItemDefinition(shadowExistsDef); mappingHelper.evaluateMappingSetProjection(params, task, result); - -// PrismValueDeltaSetTriple> outputTriple = mappingHelper.evaluateMappingSetProjection( -// outbound, "outbound existence mapping in projection " + accCtxDesc, -// now, initializer, null, null, accCtx.getObjectOld(), current, null, context, accCtx, task, result); return (boolean) output.getValue(); @@ -713,7 +705,76 @@ private ItemDeltaItem,PrismPr return new ItemDeltaItem,PrismPropertyDefinition>(existsPropOld, existsDelta, existsProp); } } - + + public void processLifecycle(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + // We can do this only for focal object. + return; + } + + processLifecycleFocus((LensContext)context, projCtx, now, task, result); + } + + private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + return; + } + + ResourceObjectLifecycleDefinitionType lifecycleDef = null; + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType != null) { + lifecycleDef = resourceAccountDefType.getLifecycle(); + } + ResourceBidirectionalMappingType lifecycleStateMappingType = null; + if (lifecycleDef != null) { + lifecycleStateMappingType = lifecycleDef.getLifecycleState(); + } + + if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { + + if (!projCtx.isAdd()) { + return; + } + + PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + return; + } + + PrismObject projectionNew = projCtx.getObjectNew(); + if (projectionNew == null) { + return; + } + + String lifecycle = midpointFunctions.computeProjectionLifecycle( + focusNew.asObjectable(), projectionNew.asObjectable(), projCtx.getResource()); + + LOGGER.trace("Computed projection lifecycle (default expression): {}", lifecycle); + + if (lifecycle != null) { + PrismPropertyDefinition propDef = projCtx.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_LIFECYCLE_STATE); + PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); + PrismPropertyValue pval = new PrismPropertyValue(lifecycle); + pval.setOriginType(OriginType.OUTBOUND); + lifeCycleDelta.setValuesToReplace(pval); + projCtx.swallowToSecondaryDelta(lifeCycleDelta); + } + + } else { + + evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, + SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, + null, now, false, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); + } + + } + private PrismObjectDefinition getUserDefinition() { if (userDefinition == null) { userDefinition = prismContext.getSchemaRegistry() 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 79d2137035a..97735469ddc 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 @@ -180,7 +180,7 @@ void processPasswordPolicy(LensFocusContext focusContex } ValuePolicyType passwordPolicy = determinePasswordPolicy(focusContext, task, result); - processPasswordPolicy(passwordPolicy, focusContext.getObjectOld(), null, passwordValueProperty, task, result); + processPasswordPolicy(focusContext, passwordPolicy, passwordValueProperty, task, result); if (passwordValueProperty != null && isPasswordChange) { processPasswordHistoryDeltas(focusContext, context, focusContext.getSecurityPolicy(), now, task, result); @@ -228,7 +228,7 @@ public ValuePolicyType determinePasswordPolicy(LensFocusC return passwordPolicy.asObjectable(); } - private void processPasswordPolicy(ValuePolicyType passwordPolicy, PrismObject focus, PrismObject projection, PrismProperty passwordProperty, + private void processPasswordPolicy(LensFocusContext focusContext, ValuePolicyType passwordPolicy, PrismProperty passwordProperty, Task task, OperationResult result) throws PolicyViolationException, SchemaException, ObjectNotFoundException, ExpressionEvaluationException { if (passwordPolicy == null) { @@ -237,14 +237,9 @@ private void processPasswordPolicy(ValuePolicyType passwor } String passwordValue = determinePasswordValue(passwordProperty); - PasswordType currentPasswordType = determineCurrentPassword(focus); + PasswordType currentPasswordType = determineCurrentPassword(focusContext.getObjectOld()); - boolean isValid; - if (projection != null) { - isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, projection, "projection password policy", task, result); - } else { - isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, focus, "focus password policy", task, result); - } + boolean isValid = validatePassword(passwordValue, currentPasswordType, passwordPolicy, focusContext.getObjectAny(), "focus password policy", task, result); if (!isValid) { result.computeStatus(); @@ -326,7 +321,19 @@ void processPasswordPolicy(LensProjectionContext projecti accountShadow = projectionContext.getObjectNew(); } - processPasswordPolicy(passwordPolicy, null, accountShadow, password, task, result); + if (passwordPolicy == null) { + LOGGER.trace("Skipping processing password policies. Password policy not specified."); + return; + } + + String passwordValue = determinePasswordValue(password); + + boolean isValid = validatePassword(passwordValue, null, passwordPolicy, accountShadow, "projection password policy", task, result); + + if (!isValid) { + result.computeStatus(); + throw new PolicyViolationException("Provided password does not satisfy password policies. " + result.getMessage()); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java index 312f0883d04..8187ee2b324 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java @@ -191,7 +191,7 @@ private void projectInternal(LensContext context, Stri context.setFresh(true); if (consistencyChecks) context.checkConsistence(); }, - partialProcessingOptions::getLoad); + partialProcessingOptions::getLoad, result); } // For now let's pretend to do just one wave. The maxWaves number will be corrected in the // first wave when dependencies are sorted out for the first time. @@ -225,7 +225,7 @@ private void projectInternal(LensContext context, Stri context.recomputeFocus(); if (consistencyChecks) context.checkConsistence(); }, - partialProcessingOptions::getFocus); + partialProcessingOptions::getFocus, result); LensUtil.traceContext(LOGGER, activityDescription, "focus processing", false, context, false); LensUtil.checkContextSanity(context, "focus processing", result); @@ -343,7 +343,6 @@ private void projectProjection(LensContext context, Le if (consistencyChecks) context.checkConsistence(); projectionContext.recompute(); - //SynchronizerUtil.traceContext("values", context, false); if (consistencyChecks) context.checkConsistence(); }, partialProcessingOptions::getProjectionValues); @@ -352,7 +351,6 @@ private void projectProjection(LensContext context, Le LensUtil.partialExecute("projectionCredentials", () -> { credentialsProcessor.processProjectionCredentials(context, projectionContext, now, task, result); - //SynchronizerUtil.traceContext("credentials", context, false); if (consistencyChecks) context.checkConsistence(); projectionContext.recompute(); @@ -372,6 +370,17 @@ private void projectProjection(LensContext context, Le partialProcessingOptions::getProjectionReconciliation); + LensUtil.partialExecute("projectionLifecycle", + () -> { + activationProcessor.processLifecycle(context, projectionContext, now, task, result); + if (consistencyChecks) context.checkConsistence(); + + projectionContext.recompute(); + LensUtil.traceContext(LOGGER, activityDescription, "projection lifecycle of "+projectionDesc, false, context, false); + if (consistencyChecks) context.checkConsistence(); + }, + partialProcessingOptions::getProjectionLifecycle); + result.recordSuccess(); } catch (ObjectNotFoundException | CommunicationException | SchemaException | ConfigurationException | SecurityViolationException diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java index 5df5f75c995..413beef848c 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestModelServiceContract.java @@ -1132,7 +1132,7 @@ public void test131ModifyUserJackAssignAccount() throws Exception { TestUtil.assertSuccess("executeChanges result", result); XMLGregorianCalendar endTime = clock.currentTimeXMLGregorianCalendar(); assertShadowFetchOperationCountIncrement(0); - assertPrismObjectCloneIncrement(64); + assertPrismObjectCloneIncrement(65); PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java index 1e5994d4b67..b4c847763e2 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/AbstractPasswordTest.java @@ -39,10 +39,13 @@ import com.evolveum.icf.dummy.resource.ConflictException; import com.evolveum.icf.dummy.resource.SchemaViolationException; import com.evolveum.midpoint.model.intest.AbstractInitializedModelIntegrationTest; +import com.evolveum.midpoint.prism.Objectable; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismReferenceValue; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; @@ -95,10 +98,10 @@ public abstract class AbstractPasswordTest extends AbstractInitializedModelInteg protected ResourceType resourceDummyUglyType; protected PrismObject resourceDummyUgly; - protected String accountOid; - protected String accountRedOid; - protected String accountUglyOid; - protected String accountYellowOid; + protected String accountJackOid; + protected String accountJackRedOid; + protected String accountJackUglyOid; + protected String accountJackYellowOid; protected XMLGregorianCalendar lastPasswordChangeStart; protected XMLGregorianCalendar lastPasswordChangeEnd; @@ -290,15 +293,17 @@ public void test100ModifyUserJackAssignAccount() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertUserJack(userJack); - accountOid = getSingleLinkOid(userJack); + accountJackOid = getSingleLinkOid(userJack); // Check shadow - PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); - assertDummyAccountShadowRepo(accountShadow, accountOid, "jack"); + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountJackOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountJackOid, "jack"); + assertShadowLifecycle(accountShadow, false); // Check account - PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); - assertDummyAccountShadowModel(accountModel, accountOid, "jack", "Jack Sparrow"); + PrismObject accountModel = modelService.getObject(ShadowType.class, accountJackOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountJackOid, "jack", "Jack Sparrow"); + assertShadowLifecycle(accountModel, false); // Check account in dummy resource assertDefaultDummyAccount(ACCOUNT_JACK_DUMMY_USERNAME, "Jack Sparrow", true); @@ -353,7 +358,7 @@ public void test111ModifyAccountJackPassword() throws Exception { assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL); // WHEN - modifyAccountChangePassword(accountOid, USER_PASSWORD_3_CLEAR, task, result); + modifyAccountChangePassword(accountJackOid, USER_PASSWORD_3_CLEAR, task, result); // THEN result.computeStatus(); @@ -391,7 +396,7 @@ public void test112ModifyJackPasswordUserAndAccount() throws Exception { ProtectedStringType userPasswordPs5 = new ProtectedStringType(); userPasswordPs5.setClearValue(USER_PASSWORD_5_CLEAR); - ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountOid, getDummyResourceObject(), + ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountJackOid, getDummyResourceObject(), PASSWORD_VALUE_PATH, userPasswordPs5); Collection> deltas = MiscSchemaUtil.createCollection(accountDelta, userDelta); @@ -444,8 +449,8 @@ public void test120ModifyUserJackAssignAccountDummyRedAndUgly() throws Exception display("User after change execution", userJack); assertUserJack(userJack); assertLinks(userJack, 3); - accountRedOid = getLinkRefOid(userJack, RESOURCE_DUMMY_RED_OID); - accountUglyOid = getLinkRefOid(userJack, RESOURCE_DUMMY_UGLY_OID); + accountJackRedOid = getLinkRefOid(userJack, RESOURCE_DUMMY_RED_OID); + accountJackUglyOid = getLinkRefOid(userJack, RESOURCE_DUMMY_UGLY_OID); // Check account in dummy resource assertDummyAccount(RESOURCE_DUMMY_RED_NAME, ACCOUNT_JACK_DUMMY_USERNAME, "Jack Sparrow", true); @@ -481,7 +486,7 @@ public void test121ModifyJackPasswordUserAndAccountRed() throws Exception { ProtectedStringType userPasswordPs2 = new ProtectedStringType(); userPasswordPs2.setClearValue(USER_PASSWORD_2_CLEAR); - ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountRedOid, getDummyResourceObject(), + ObjectDelta accountDelta = createModifyAccountShadowReplaceDelta(accountJackRedOid, getDummyResourceObject(), PASSWORD_VALUE_PATH, userPasswordPs2); Collection> deltas = MiscSchemaUtil.createCollection(userDelta, accountDelta); @@ -617,7 +622,7 @@ public void test130ModifyUserJackAssignAccountDummyYellow() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); // Check account in dummy resource (yellow) assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); @@ -667,7 +672,7 @@ public void test132ModifyUserJackPasswordA() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); // Check account in dummy resource (yellow): password is too short for this, original password should remain there assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); @@ -736,7 +741,7 @@ public void test202ReconcileUserJack() throws Exception { PrismObject userAfter = getUser(USER_JACK_OID); display("User after", userAfter); assertLinks(userAfter, 4); - accountYellowOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_YELLOW_OID); // Check account in dummy resource (yellow): password is too short for this, original password should remain there assertDummyAccount(RESOURCE_DUMMY_YELLOW_NAME, ACCOUNT_JACK_DUMMY_USERNAME, ACCOUNT_JACK_DUMMY_FULLNAME, true); @@ -1006,7 +1011,7 @@ private void assertJackPasswordsWithHistory(String expectedCurrentPassword, Stri PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); assertUserPassword(userJack, expectedCurrentPassword); assertPasswordMetadata(userJack, false, lastPasswordChangeStart, lastPasswordChangeEnd); @@ -1074,7 +1079,7 @@ public void test300TwoParentOrgRefs() throws Exception { PrismObject userJack = getUser(USER_JACK_OID); display("User after change execution", userJack); assertLinks(userJack, 4); - accountYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); + accountJackYellowOid = getLinkRefOid(userJack, RESOURCE_DUMMY_YELLOW_OID); // Make sure that the password is unchanged @@ -1174,9 +1179,11 @@ public void test330RemoveEmployeeNumber() throws Exception { assumeAssignmentPolicy(AssignmentPolicyEnforcementType.FULL); // WHEN + TestUtil.displayWhen(TEST_NAME); modifyUserReplace(USER_JACK_OID, UserType.F_EMPLOYEE_NUMBER, task, result); // THEN + TestUtil.displayThen(TEST_NAME); result.computeStatus(); TestUtil.assertSuccess(result); @@ -1191,6 +1198,115 @@ public void test330RemoveEmployeeNumber() throws Exception { assertDummyPassword(RESOURCE_DUMMY_UGLY_NAME, ACCOUNT_JACK_DUMMY_USERNAME, null); } + + /** + * Add user with password and an assignment. check that the account is provisioned and has password. + * Tests proper initial cleartext password handling in all cases. + */ + @Test + public void test400AddUserRappWithAssignment() throws Exception { + final String TEST_NAME = "test400AddUserRappWithAssignment"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = PrismTestUtil.parseObject(USER_RAPP_FILE); + AssignmentType assignmentType = createConstructionAssignment(RESOURCE_DUMMY_OID, null, null); + UserType userBeforeType = userBefore.asObjectable(); + userBeforeType.setFullName(createPolyStringType(USER_RAPP_FULLNAME)); + userBeforeType.getAssignment().add(assignmentType); + setPassword(userBefore, USER_PASSWORD_VALID_1); + display("User before", userBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + addObject(userBefore, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + String accountOid = getSingleLinkOid(userAfter); + + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, true); + + // Check account + PrismObject accountModel = modelService.getObject(ShadowType.class, accountOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, true); + + // Check account in dummy resource + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + /** + * add new assignment to the user, check that account is sprovisioned and has correct lifecycle + */ + @Test + public void test402AssignRappDummyRed() throws Exception { + final String TEST_NAME = "test402AssignRappDummyRed"; + TestUtil.displayTestTile(this, TEST_NAME); + + // GIVEN + Task task = taskManager.createTaskInstance(AbstractPasswordTest.class.getName() + "." + TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = getUser(USER_RAPP_OID); + display("User before", userBefore); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + assignAccount(USER_RAPP_OID, RESOURCE_DUMMY_RED_OID, null, task, result); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + TestUtil.assertSuccess(result); + + PrismObject userAfter = getUser(USER_RAPP_OID); + display("User after", userAfter); + + String accountDefaultOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_OID); + String accountRedOid = getLinkRefOid(userAfter, RESOURCE_DUMMY_RED_OID); + + // Check account in dummy resource + assertDummyAccount(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPasswordConditional(RESOURCE_DUMMY_RED_NAME, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + + // RED shadows + PrismObject accountShadowRed = repositoryService.getObject(ShadowType.class, accountRedOid, null, result); + display("Repo shadow RED", accountShadowRed); + assertAccountShadowRepo(accountShadowRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountShadowRed, false); + + PrismObject accountModelRed = modelService.getObject(ShadowType.class, accountRedOid, null, task, result); + display("Model shadow RED", accountModelRed); + assertAccountShadowModel(accountModelRed, accountRedOid, USER_RAPP_USERNAME, getDummyResourceType(RESOURCE_DUMMY_RED_NAME)); + assertShadowLifecycle(accountModelRed, false); + + // DEFAULT shadows + PrismObject accountShadow = repositoryService.getObject(ShadowType.class, accountDefaultOid, null, result); + assertDummyAccountShadowRepo(accountShadow, accountDefaultOid, USER_RAPP_USERNAME); + assertShadowLifecycle(accountShadow, true); + + PrismObject accountModel = modelService.getObject(ShadowType.class, accountDefaultOid, null, task, result); + assertDummyAccountShadowModel(accountModel, accountDefaultOid, USER_RAPP_USERNAME, USER_RAPP_FULLNAME); + assertShadowLifecycle(accountModel, true); + + assertUserPassword(userAfter, USER_PASSWORD_VALID_1); + assertDefaultDummyAccount(USER_RAPP_USERNAME, USER_RAPP_FULLNAME, true); + assertDummyPassword(null, USER_RAPP_USERNAME, USER_PASSWORD_VALID_1); + } + + // TODO: initialize the account (password and lifecycle delta), check accoutn password and lifecycle private void assertDummyPassword(String userId, String expectedClearPassword) throws SchemaViolationException, ConflictException { @@ -1209,4 +1325,16 @@ protected void assertDummyPasswordConditional(String instance, String userId, St } } + protected abstract void assertShadowLifecycle(PrismObject shadow, boolean focusCreated); + + protected void assertShadowLifecycle(PrismObject shadow, String expectedLifecycle) { + if (expectedLifecycle == null) { + String actualLifecycle = shadow.asObjectable().getLifecycleState(); + if (actualLifecycle != null && !SchemaConstants.LIFECYCLE_ACTIVE.equals(actualLifecycle)) { + fail("Expected default lifecycle for "+shadow+", but was "+actualLifecycle); + } + } else { + PrismAsserts.assertPropertyValue(shadow, ObjectType.F_LIFECYCLE_STATE, expectedLifecycle); + } + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java index 53b183659aa..4fa81b5062c 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefault.java @@ -22,8 +22,10 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; +import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Password test with DEFAULT configuration of password storage. @@ -45,5 +47,10 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti protected String getSecurityPolicyOid() { return SECURITY_POLICY_OID; } + + @Override + protected void assertShadowLifecycle(PrismObject shadow, boolean focusCreated) { + assertShadowLifecycle(shadow, null); + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java index e1d7993f8b8..6b3bd88eea0 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordDefaultHashing.java @@ -22,9 +22,12 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsStorageTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Password test with HASHING storage for all credential types. @@ -49,6 +52,15 @@ protected String getSecurityPolicyOid() { @Override protected CredentialsStorageTypeType getPasswordStorageType() { return CredentialsStorageTypeType.HASHING; - } + } + + @Override + protected void assertShadowLifecycle(PrismObject shadow, boolean focusCreated) { + if (focusCreated) { + assertShadowLifecycle(shadow, null); + } else { + assertShadowLifecycle(shadow, SchemaConstants.LIFECYCLE_PROPOSED); + } + } } diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java index 0173cf133ba..1a7cec36e4e 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/password/TestPasswordNone.java @@ -22,9 +22,12 @@ import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsStorageTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; /** * Password test with NONE password storage (default storage for other types) @@ -56,4 +59,13 @@ protected String getSecurityPolicyOid() { protected CredentialsStorageTypeType getPasswordStorageType() { return CredentialsStorageTypeType.NONE; } + + @Override + protected void assertShadowLifecycle(PrismObject shadow, boolean focusCreated) { + if (focusCreated) { + assertShadowLifecycle(shadow, null); + } else { + assertShadowLifecycle(shadow, SchemaConstants.LIFECYCLE_PROPOSED); + } + } } diff --git a/model/model-intest/src/test/resources/common/password-policy-global.xml b/model/model-intest/src/test/resources/common/password-policy-global.xml index b9c2c61cc7a..6b5b6a2f3fb 100644 --- a/model/model-intest/src/test/resources/common/password-policy-global.xml +++ b/model/model-intest/src/test/resources/common/password-policy-global.xml @@ -39,7 +39,7 @@ assert input != null assert object != null - assert object.getName() != null + // object.getName() may be null (e.g. new shadow) if (object instanceof com.evolveum.midpoint.xml.ns._public.common.common_3.UserType) { return !basic.containsIgnoreCase(input, object.getName()) } else { @@ -56,7 +56,7 @@ assert input != null assert object != null - assert object.getName() != null + // object.getName() may be null (e.g. new shadow) if (object instanceof com.evolveum.midpoint.xml.ns._public.common.common_3.UserType) { return !basic.containsIgnoreCase(input, object.getFamilyName()) && !basic.containsIgnoreCase(input, object.getGivenName()) && !basic.containsIgnoreCase(input, object.getAdditionalName()) } else { diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index 417362f6201..5c12997a410 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -1464,4 +1464,21 @@ protected void logTrustManagers() throws NoSuchAlgorithmException, KeyStoreExcep } } } + + protected void setPassword(PrismObject user, String password) { + UserType userType = user.asObjectable(); + CredentialsType creds = userType.getCredentials(); + if (creds == null) { + creds = new CredentialsType(); + userType.setCredentials(creds); + } + PasswordType passwordType = creds.getPassword(); + if (passwordType == null) { + passwordType = new PasswordType(); + creds.setPassword(passwordType); + } + ProtectedStringType ps = new ProtectedStringType(); + ps.setClearValue(password); + passwordType.setValue(ps); + } }