/* * 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. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.model.impl.lens.projector.credentials; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.common.expression.ItemDeltaItem; import com.evolveum.midpoint.model.common.expression.Source; import com.evolveum.midpoint.model.common.expression.StringPolicyResolver; import com.evolveum.midpoint.model.common.mapping.Mapping; import com.evolveum.midpoint.model.common.mapping.Mapping.Builder; import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.common.stringpolicy.ObjectValuePolicyEvaluator; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.ModelObjectResolver; import com.evolveum.midpoint.model.impl.lens.LensContext; 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.model.impl.lens.OperationalDataManager; import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; import com.evolveum.midpoint.model.impl.lens.projector.MappingEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.MappingInitializer; import com.evolveum.midpoint.model.impl.lens.projector.MappingOutputProcessor; import com.evolveum.midpoint.model.impl.security.SecurityHelper; import com.evolveum.midpoint.model.impl.util.Utils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.crypto.EncryptionException; import com.evolveum.midpoint.prism.crypto.Protector; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.path.ItemPath; 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.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.util.SchemaFailableProcessor; import com.evolveum.midpoint.util.exception.CommunicationException; import com.evolveum.midpoint.util.exception.ConfigurationException; import com.evolveum.midpoint.util.exception.ExpressionEvaluationException; import com.evolveum.midpoint.util.exception.ObjectNotFoundException; import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SecurityViolationException; 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.*; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PasswordCapabilityType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; import org.apache.commons.lang.Validate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import java.util.*; import java.util.function.Consumer; import static com.evolveum.midpoint.prism.delta.ChangeType.MODIFY; import static com.evolveum.midpoint.xml.ns._public.common.common_3.MappingStrengthType.WEAK; /** * Processor for projection credentials. Which at this moment means just the password. * * @author Radovan Semancik */ @Component public class ProjectionCredentialsProcessor { private static final Trace LOGGER = TraceManager.getTrace(ProjectionCredentialsProcessor.class); @Autowired(required=true) private PrismContext prismContext; @Autowired(required=true) private ContextLoader contextLoader; @Autowired(required=true) private MappingFactory mappingFactory; @Autowired(required=true) private MappingEvaluator mappingEvaluator; @Autowired(required=true) private ValuePolicyProcessor valuePolicyProcessor; @Autowired(required = true) private Protector protector; @Autowired(required = true) private OperationalDataManager operationalDataManager; public <F extends ObjectType> void processProjectionCredentials(LensContext<F> context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { if (projectionContext.isDelete()) { return; } LensFocusContext<F> focusContext = context.getFocusContext(); if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { processProjectionCredentialsFocus((LensContext<? extends FocusType>) context, projectionContext, now, task, result); } } public <F extends FocusType> void processProjectionCredentialsFocus(LensContext<F> context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { ValuePolicyType passwordPolicy = determinePasswordPolicy(context, projectionContext, now, task, result); processProjectionPasswordMapping(context, projectionContext, passwordPolicy, now, task, result); validateProjectionPassword(context, projectionContext, passwordPolicy, now, task, result); applyMetadata(context, projectionContext, now, task, result); } private <F extends FocusType> void processProjectionPasswordMapping(LensContext<F> context, final LensProjectionContext projCtx, final ValuePolicyType passwordPolicy, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { LensFocusContext<F> focusContext = context.getFocusContext(); PrismObject<F> 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<ShadowType> accountDefinition = prismContext.getSchemaRegistry() .findObjectDefinitionByCompileTimeClass(ShadowType.class); PrismPropertyDefinition<ProtectedStringType> projPasswordPropertyDefinition = accountDefinition .findPropertyDefinition(SchemaConstants.PATH_PASSWORD_VALUE); ResourceShadowDiscriminator rsd = projCtx.getResourceShadowDiscriminator(); RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); if (refinedProjDef == null) { LOGGER.trace("No RefinedObjectClassDefinition, therefore also no password outbound definition, skipping credentials processing for projection {}", rsd); return; } List<MappingType> outboundMappingTypes = refinedProjDef.getPasswordOutbound(); if (outboundMappingTypes == null || outboundMappingTypes.isEmpty()) { LOGGER.trace("No outbound password mapping for {}, skipping credentials processing", rsd); return; } // HACK if (!projCtx.isDoReconciliation() && !projCtx.isAdd() && !isActivated(outboundMappingTypes, focusContext.getDelta())) { LOGGER.trace("Outbound password mappings not activated for type {}, skipping credentials processing", rsd); return; } final ObjectDelta<ShadowType> projDelta = projCtx.getDelta(); final PropertyDelta<ProtectedStringType> projPasswordDelta; if (projDelta != null && projDelta.getChangeType() == MODIFY) { projPasswordDelta = projDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); } else { projPasswordDelta = null; } checkExistingDeltaSanity(projCtx, projPasswordDelta); boolean evaluateWeak = getEvaluateWeak(projCtx); final ItemDeltaItem<PrismPropertyValue<PasswordType>, PrismPropertyDefinition<ProtectedStringType>> userPasswordIdi = focusContext .getObjectDeltaObject().findIdi(SchemaConstants.PATH_PASSWORD_VALUE); StringPolicyResolver stringPolicyResolver = new StringPolicyResolver() { @Override public void setOutputPath(ItemPath outputPath) { } @Override public void setOutputDefinition(ItemDefinition outputDefinition) { } @Override public StringPolicyType resolve() { if (passwordPolicy == null) { return null; } return passwordPolicy.getStringPolicy(); } }; MappingInitializer<PrismPropertyValue<ProtectedStringType>,PrismPropertyDefinition<ProtectedStringType>> initializer = (builder) -> { builder.defaultTargetDefinition(projPasswordPropertyDefinition); builder.defaultSource(new Source<>(userPasswordIdi, ExpressionConstants.VAR_INPUT)); builder.stringPolicyResolver(stringPolicyResolver); return builder; }; MappingOutputProcessor<PrismPropertyValue<ProtectedStringType>> processor = (mappingOutputPath, outputStruct) -> { PrismValueDeltaSetTriple<PrismPropertyValue<ProtectedStringType>> outputTriple = outputStruct.getOutputTriple(); if (outputTriple == null) { LOGGER.trace("Credentials 'password' expression resulted in null output triple, skipping credentials processing for {}", rsd); return false; } boolean projectionIsNew = projDelta != null && (projDelta.getChangeType() == ChangeType.ADD || projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.ADD); Collection<PrismPropertyValue<ProtectedStringType>> newValues = outputTriple.getPlusSet(); if (projectionIsNew) { newValues = outputTriple.getNonNegativeValues(); } else { newValues = outputTriple.getPlusSet(); } if (!canGetCleartext(newValues)) { ObjectDelta<ShadowType> projectionPrimaryDelta = projCtx.getPrimaryDelta(); if (projectionPrimaryDelta != null) { PropertyDelta<ProtectedStringType> passwordPrimaryDelta = projectionPrimaryDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); if (passwordPrimaryDelta != null) { // We have only hashed value coming from the mapping. There are not very useful // for provisioning. But we have primary projection delta - and that is very likely // to be better. // Skip all password mappings in this case. Primary delta trumps everything. // No weak, normal or even strong mapping can change that. // We need to disregard even strong mapping in this case. If we would heed the strong // mapping then account initialization won't be possible. LOGGER.trace("We have primary password delta in projection, skipping credentials processing"); return false; } } } return true; }; mappingEvaluator.evaluateOutboundMapping(context, projCtx, outboundMappingTypes, SchemaConstants.PATH_PASSWORD_VALUE, SchemaConstants.PATH_PASSWORD_VALUE, initializer, processor, now, true, evaluateWeak, "password mapping", task, result); } private <F extends FocusType> boolean isActivated(List<MappingType> outboundMappingTypes, ObjectDelta<F> focusDelta) { if (focusDelta == null) { return false; } for (MappingType outboundMappingType: outboundMappingTypes) { List<VariableBindingDefinitionType> sources = outboundMappingType.getSource(); if (sources.isEmpty()) { // Default source if (focusDelta.hasItemDelta(SchemaConstants.PATH_PASSWORD_VALUE)) { return true; } } for (VariableBindingDefinitionType source: sources) { ItemPathType pathType = source.getPath(); ItemPath path = pathType.getItemPath().stripVariableSegment(); if (focusDelta.hasItemDelta(path)) { return true; } } } return false; } private boolean canGetCleartext(Collection<PrismPropertyValue<ProtectedStringType>> pvals) { if (pvals == null) { return false; } for (PrismPropertyValue<ProtectedStringType> pval: pvals) { if (pval.getValue().canGetCleartext()) { return true; } } return false; } private boolean getEvaluateWeak(LensProjectionContext projCtx) { CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); if (credentialsCapabilityType != null) { PasswordCapabilityType passwordCapabilityType = credentialsCapabilityType.getPassword(); if (passwordCapabilityType != null) { if (passwordCapabilityType.isEnabled() != Boolean.FALSE) { Boolean readable = passwordCapabilityType.isReadable(); if (readable != null && readable) { // If we have readable password then we can evaluate the weak mappings // normally (even if the reads return incomplete values). return true; } } } } // Password not readable. Therefore evaluate weak mappings only during add operaitons. // We do not know whether there is a password already set on the resource. And we do not // want to overwrite it every time. return projCtx.isAdd(); } private <F extends FocusType> void validateProjectionPassword(LensContext<F> context, final LensProjectionContext projectionContext, final ValuePolicyType passwordPolicy, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { if (passwordPolicy == null) { return; } ObjectDelta<ShadowType> accountDelta = projectionContext.getDelta(); if (accountDelta == null){ LOGGER.trace("Skipping processing password policies. Shadow delta not specified."); return; } if (accountDelta.isDelete()) { return; } PrismObject<ShadowType> accountShadow = null; PrismProperty<ProtectedStringType> password = null; if (accountDelta.isAdd()) { accountShadow = accountDelta.getObjectToAdd(); if (accountShadow != null){ password = accountShadow.findProperty(SchemaConstants.PATH_PASSWORD_VALUE); } } if (accountDelta.isModify() || password == null) { PropertyDelta<ProtectedStringType> passwordValueDelta = accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); // Modification sanity check if (accountDelta.getChangeType() == ChangeType.MODIFY && passwordValueDelta != null && (passwordValueDelta.isAdd() || passwordValueDelta.isDelete())) { throw new SchemaException("Shadow password value cannot be added or deleted, it can only be replaced"); } if (passwordValueDelta == null) { LOGGER.trace("Skipping processing password policies. Shadow delta does not contain password change."); return; } password = (PrismProperty<ProtectedStringType>) passwordValueDelta.getItemNewMatchingPath(null); } if (accountShadow == null) { accountShadow = projectionContext.getObjectNew(); } if (passwordPolicy == null) { LOGGER.trace("Skipping processing password policies. Password policy not specified."); return; } String passwordValue = determinePasswordValue(password); boolean isValid = valuePolicyProcessor.validateValue(passwordValue, passwordPolicy, accountShadow, "projection password policy", task, result); if (!isValid) { result.computeStatus(); throw new PolicyViolationException("Provided password does not satisfy password policies in " + projectionContext.getHumanReadableName() + ": " + result.getMessage()); } } private <F extends FocusType> void applyMetadata(LensContext<F> context, final LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { ObjectDelta<ShadowType> accountDelta = projectionContext.getDelta(); if (projectionContext.isDelete()) { return; } if (accountDelta == null){ LOGGER.trace("Skipping application of password metadata. Shadow delta not specified."); return; } PropertyDelta<ProtectedStringType> passwordValueDelta = accountDelta.findPropertyDelta(SchemaConstants.PATH_PASSWORD_VALUE); if (passwordValueDelta == null) { LOGGER.trace("Skipping application of password metadata. No password change."); return; } if (projectionContext.isAdd()) { MetadataType metadataType = operationalDataManager.createCreateMetadata(context, now, task); ContainerDelta<MetadataType> metadataDelta = ContainerDelta.createDelta(SchemaConstants.PATH_PASSWORD_METADATA, projectionContext.getObjectDefinition()); PrismContainerValue cval = metadataType.asPrismContainerValue(); cval.setOriginTypeRecursive(OriginType.OUTBOUND); metadataDelta.addValuesToAdd(metadataType.asPrismContainerValue()); projectionContext.swallowToSecondaryDelta(metadataDelta); } else if (projectionContext.isModify()) { ContainerDelta<MetadataType> metadataDelta = accountDelta.findContainerDelta(SchemaConstants.PATH_PASSWORD_METADATA); if (metadataDelta == null) { Collection<? extends ItemDelta<?,?>> modifyMetadataDeltas = operationalDataManager.createModifyMetadataDeltas(context, SchemaConstants.PATH_PASSWORD_METADATA, projectionContext.getObjectDefinition(), now, task); for (ItemDelta itemDelta: modifyMetadataDeltas) { itemDelta.setOriginTypeRecursive(OriginType.OUTBOUND); projectionContext.swallowToSecondaryDelta(itemDelta); } } } } private <F extends FocusType> ValuePolicyType determinePasswordPolicy(LensContext<F> context, final LensProjectionContext projCtx, XMLGregorianCalendar now, Task task, OperationResult result) { ValuePolicyType passwordPolicy = projCtx.getAccountPasswordPolicy(); if (passwordPolicy != null) { return passwordPolicy; } LensFocusContext<F> focusContext = context.getFocusContext(); if (focusContext == null) { return null; } return SecurityUtil.getPasswordPolicy(focusContext.getSecurityPolicy()); } // On missing password this returns empty string (""). It is then up to password policy whether it allows empty passwords or not. private String determinePasswordValue(PrismProperty<ProtectedStringType> password) { if (password == null || password.getValue(ProtectedStringType.class) == null) { return null; } ProtectedStringType passValue = password.getRealValue(); return determinePasswordValue(passValue); } private String determinePasswordValue(ProtectedStringType passValue) { if (passValue == null) { return null; } String passwordStr = passValue.getClearValue(); if (passwordStr == null && passValue.getEncryptedDataType () != null) { // TODO: is this appropriate handling??? try { passwordStr = protector.decryptString(passValue); } catch (EncryptionException ex) { throw new SystemException("Failed to process password for user: " , ex); } } return passwordStr; } private void checkExistingDeltaSanity(LensProjectionContext projCtx, PropertyDelta<ProtectedStringType> passwordDelta) throws SchemaException { if (passwordDelta != null && (passwordDelta.isAdd() || passwordDelta.isDelete())) { throw new SchemaException("Password for projection " + projCtx.getResourceShadowDiscriminator() + " cannot be added or deleted, it can only be replaced"); } } }