com.evolveum.midpoint.model.impl.controller.ModelInteractionServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.model.impl.controller.ModelInteractionServiceImpl.java

Source

/*
 * 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.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.xml.namespace.QName;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import com.evolveum.midpoint.common.ActivationComputer;
import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.common.refinery.CompositeRefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.LayerRefinedAttributeDefinition;
import com.evolveum.midpoint.common.refinery.LayerRefinedAttributeDefinitionImpl;
import com.evolveum.midpoint.common.refinery.LayerRefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchema;
import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl;
import com.evolveum.midpoint.model.api.ModelAuthorizationAction;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ModelInteractionService;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.ProgressListener;
import com.evolveum.midpoint.model.api.RoleSelectionSpecification;
import com.evolveum.midpoint.model.api.context.EvaluatedAssignment;
import com.evolveum.midpoint.model.api.context.EvaluatedAssignmentTarget;
import com.evolveum.midpoint.model.api.context.ModelContext;
import com.evolveum.midpoint.model.api.hooks.ChangeHook;
import com.evolveum.midpoint.model.api.hooks.HookRegistry;
import com.evolveum.midpoint.model.api.util.DeputyUtils;
import com.evolveum.midpoint.model.api.util.MergeDeltas;
import com.evolveum.midpoint.model.api.visualizer.Scene;
import com.evolveum.midpoint.model.common.SystemObjectCache;
import com.evolveum.midpoint.model.common.mapping.MappingFactory;
import com.evolveum.midpoint.model.common.stringpolicy.AbstractValuePolicyOriginResolver;
import com.evolveum.midpoint.model.common.stringpolicy.ObjectValuePolicyEvaluator;
import com.evolveum.midpoint.model.common.stringpolicy.ShadowValuePolicyOriginResolver;
import com.evolveum.midpoint.model.common.stringpolicy.UserValuePolicyOriginResolver;
import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor;
import com.evolveum.midpoint.model.impl.ModelCrudService;
import com.evolveum.midpoint.model.impl.ModelObjectResolver;
import com.evolveum.midpoint.model.impl.lens.AssignmentEvaluator;
import com.evolveum.midpoint.model.impl.lens.ContextFactory;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensContextPlaceholder;
import com.evolveum.midpoint.model.impl.lens.LensUtil;
import com.evolveum.midpoint.model.impl.lens.OperationalDataManager;
import com.evolveum.midpoint.model.impl.lens.projector.MappingEvaluator;
import com.evolveum.midpoint.model.impl.lens.projector.Projector;
import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor;
import com.evolveum.midpoint.model.impl.lens.projector.credentials.PasswordPolicyEvaluator;
import com.evolveum.midpoint.model.impl.security.SecurityHelper;
import com.evolveum.midpoint.model.impl.visualizer.Visualizer;
import com.evolveum.midpoint.prism.ComplexTypeDefinitionImpl;
import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismConstants;
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;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PlusMinusZero;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.query.AllFilter;
import com.evolveum.midpoint.prism.query.AndFilter;
import com.evolveum.midpoint.prism.query.EqualFilter;
import com.evolveum.midpoint.prism.query.NoneFilter;
import com.evolveum.midpoint.prism.query.NotFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.OrFilter;
import com.evolveum.midpoint.prism.query.RefFilter;
import com.evolveum.midpoint.prism.query.TypeFilter;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.PreconditionViolationException;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.repo.cache.RepositoryCache;
import com.evolveum.midpoint.repo.common.CacheRegistry;
import com.evolveum.midpoint.repo.common.expression.ExpressionFactory;
import com.evolveum.midpoint.repo.common.expression.ExpressionVariables;
import com.evolveum.midpoint.repo.common.expression.ItemDeltaItem;
import com.evolveum.midpoint.repo.common.expression.ObjectDeltaObject;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.RetrieveOption;
import com.evolveum.midpoint.schema.SearchResultList;
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.result.OperationResultStatus;
import com.evolveum.midpoint.schema.statistics.ConnectorOperationalStatus;
import com.evolveum.midpoint.schema.util.LocalizationUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.SecurityContextManager;
import com.evolveum.midpoint.security.api.UserProfileService;
import com.evolveum.midpoint.security.enforcer.api.ItemSecurityConstraints;
import com.evolveum.midpoint.security.enforcer.api.ObjectSecurityConstraints;
import com.evolveum.midpoint.security.enforcer.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.LocalizableMessage;
import com.evolveum.midpoint.util.LocalizableMessageBuilder;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.CommonException;
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.ObjectAlreadyExistsException;
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.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ExecuteCredentialResetRequestType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.ExecuteCredentialResetResponseType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PolicyItemDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PolicyItemTargetType;
import com.evolveum.midpoint.xml.ns._public.common.api_types_3.PolicyItemsDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractWorkItemType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AccessCertificationConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AdminGuiConfigurationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthenticationsPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationDecisionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialSourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsResetPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.DeploymentInformationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LensContextType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageTemplateType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LocalizableMessageType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableRowType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.LookupTableType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateItemDefinitionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OtherPrivilegesLimitationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.PasswordType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RegistrationsPolicyType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SecurityPolicyType;
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.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.ItemPathType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;
import com.evolveum.prism.xml.ns._public.types_3.RawType;

/**
 * @author semancik
 *
 */
@Component("modelInteractionService")
public class ModelInteractionServiceImpl implements ModelInteractionService {

    private static final Trace LOGGER = TraceManager.getTrace(ModelInteractionServiceImpl.class);

    @Autowired
    private ContextFactory contextFactory;
    @Autowired
    private Projector projector;
    @Autowired
    private SecurityEnforcer securityEnforcer;
    @Autowired
    private SecurityContextManager securityContextManager;
    @Autowired
    private SchemaTransformer schemaTransformer;
    @Autowired
    private ProvisioningService provisioning;
    @Autowired
    private ModelObjectResolver objectResolver;
    @Autowired
    private ObjectMerger objectMerger;
    @Autowired
    @Qualifier("cacheRepositoryService")
    private transient RepositoryService cacheRepositoryService;
    @Autowired
    private SystemObjectCache systemObjectCache;
    @Autowired
    private ValuePolicyProcessor policyProcessor;
    @Autowired
    private Protector protector;
    @Autowired
    private PrismContext prismContext;
    @Autowired
    private Visualizer visualizer;
    @Autowired
    private ModelService modelService;
    @Autowired
    private ModelCrudService modelCrudService;
    @Autowired
    private SecurityHelper securityHelper;
    @Autowired
    private MappingFactory mappingFactory;
    @Autowired
    private MappingEvaluator mappingEvaluator;
    @Autowired
    private ActivationComputer activationComputer;
    @Autowired
    private Clock clock;
    @Autowired
    private HookRegistry hookRegistry;
    @Autowired
    UserProfileService userProfileService;
    @Autowired
    private ExpressionFactory expressionFactory;
    @Autowired
    private CacheRegistry cacheRegistry;
    @Autowired
    private OperationalDataManager metadataManager;

    private static final String OPERATION_GENERATE_VALUE = ModelInteractionService.class.getName()
            + ".generateValue";
    private static final String OPERATION_VALIDATE_VALUE = ModelInteractionService.class.getName()
            + ".validateValue";

    /* (non-Javadoc)
     * @see com.evolveum.midpoint.model.api.ModelInteractionService#previewChanges(com.evolveum.midpoint.prism.delta.ObjectDelta, com.evolveum.midpoint.schema.result.OperationResult)
     */
    @Override
    public <F extends ObjectType> ModelContext<F> previewChanges(
            Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options, Task task,
            OperationResult parentResult) throws SchemaException, PolicyViolationException,
            ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException,
            CommunicationException, ConfigurationException, SecurityViolationException {
        return previewChanges(deltas, options, task, Collections.emptyList(), parentResult);
    }

    @Override
    public <F extends ObjectType> ModelContext<F> previewChanges(
            Collection<ObjectDelta<? extends ObjectType>> deltas, ModelExecuteOptions options, Task task,
            Collection<ProgressListener> listeners, OperationResult parentResult)
            throws SchemaException, PolicyViolationException, ExpressionEvaluationException,
            ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException,
            SecurityViolationException {

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Preview changes input:\n{}", DebugUtil.debugDump(deltas));
        }
        int size = 0;
        if (deltas != null) {
            size = deltas.size();
        }
        Collection<ObjectDelta<? extends ObjectType>> clonedDeltas = new ArrayList<>(size);
        if (deltas != null) {
            for (ObjectDelta delta : deltas) {
                clonedDeltas.add(delta.clone());
            }
        }

        OperationResult result = parentResult.createSubresult(PREVIEW_CHANGES);
        LensContext<F> context;

        try {
            RepositoryCache.enter();
            //used cloned deltas instead of origin deltas, because some of the values should be lost later..
            context = contextFactory.createContext(clonedDeltas, options, task, result);
            //         context.setOptions(options);
            LOGGER.trace("Preview changes context:\n{}", context.debugDumpLazily());
            context.setProgressListeners(listeners);

            projector.projectAllWaves(context, "preview", task, result);
            context.distributeResource();

            if (hookRegistry != null) {
                for (ChangeHook hook : hookRegistry.getAllChangeHooks()) {
                    hook.invokePreview(context, task, result);
                }
            }

        } catch (ConfigurationException | SecurityViolationException | ObjectNotFoundException | SchemaException
                | CommunicationException | PolicyViolationException | RuntimeException
                | ObjectAlreadyExistsException | ExpressionEvaluationException e) {
            ModelImplUtils.recordFatalError(result, e);
            throw e;

        } catch (PreconditionViolationException e) {
            ModelImplUtils.recordFatalError(result, e);
            // TODO: Temporary fix for 3.6.1
            // We do not want to propagate PreconditionViolationException to model API as that might break compatiblity
            // ... and we do not really need that in 3.6.1
            // TODO: expose PreconditionViolationException in 3.7
            throw new SystemException(e);

        } finally {
            RepositoryCache.exit();
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Preview changes output:\n{}", context.debugDump());
        }

        result.computeStatus();
        result.cleanupResult();

        return context;
    }

    @Override
    public <F extends ObjectType> ModelContext<F> unwrapModelContext(LensContextType wrappedContext, Task task,
            OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException,
            CommunicationException, ExpressionEvaluationException {
        return LensContext.fromLensContextType(wrappedContext, prismContext, provisioning, task, result);
    }

    @Override
    public <O extends ObjectType> PrismObjectDefinition<O> getEditObjectDefinition(PrismObject<O> object,
            AuthorizationPhaseType phase, Task task, OperationResult parentResult)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException,
            CommunicationException, SecurityViolationException {
        OperationResult result = parentResult.createMinorSubresult(GET_EDIT_OBJECT_DEFINITION);
        PrismObjectDefinition<O> objectDefinition = object.getDefinition().deepClone(true,
                schemaTransformer::setFullAccessFlags);

        try {

            PrismObject<O> baseObject = object;
            if (object.getOid() != null) {
                // Re-read the object from the repository to make sure we have all the properties.
                // the object from method parameters may be already processed by the security code
                // and properties needed to evaluate authorizations may not be there
                // MID-3126, see also MID-3435
                baseObject = cacheRepositoryService.getObject(object.getCompileTimeClass(), object.getOid(), null,
                        result);
            }

            // TODO: maybe we need to expose owner resolver in the interface?
            ObjectSecurityConstraints securityConstraints = securityEnforcer.compileSecurityConstraints(baseObject,
                    null, task, result);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Security constrains for {}:\n{}", object,
                        securityConstraints == null ? "null" : securityConstraints.debugDump());
            }
            if (securityConstraints == null) {
                // Nothing allowed => everything denied
                result.setStatus(OperationResultStatus.NOT_APPLICABLE);
                return null;
            }

            ObjectTemplateType objectTemplateType = null;
            try {
                objectTemplateType = schemaTransformer.determineObjectTemplate(object, phase, result);
            } catch (ConfigurationException | ObjectNotFoundException e) {
                result.recordFatalError(e);
            }
            schemaTransformer.applyObjectTemplateToDefinition(objectDefinition, objectTemplateType, result);

            schemaTransformer.applySecurityConstraints(objectDefinition, securityConstraints, phase);

            if (object.canRepresent(ShadowType.class)) {
                PrismObject<ShadowType> shadow = (PrismObject<ShadowType>) object;
                String resourceOid = ShadowUtil.getResourceOid(shadow);
                if (resourceOid != null) {
                    Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions
                            .createCollection(GetOperationOptions.createReadOnly());
                    PrismObject<ResourceType> resource;
                    try {
                        resource = provisioning.getObject(ResourceType.class, resourceOid, options, task, result);
                    } catch (CommunicationException | SecurityViolationException
                            | ExpressionEvaluationException e) {
                        throw new ConfigurationException(e.getMessage(), e);
                    }
                    RefinedObjectClassDefinition refinedObjectClassDefinition = getEditObjectClassDefinition(shadow,
                            resource, phase, task, result);
                    if (refinedObjectClassDefinition != null) {
                        ((ComplexTypeDefinitionImpl) objectDefinition.getComplexTypeDefinition()).replaceDefinition(
                                ShadowType.F_ATTRIBUTES,
                                refinedObjectClassDefinition.toResourceAttributeContainerDefinition());
                    }
                }
            }

            result.computeStatus();
            return objectDefinition;

        } catch (ConfigurationException | ObjectNotFoundException | ExpressionEvaluationException
                | SchemaException e) {
            result.recordFatalError(e);
            throw e;
        }

    }

    @Override
    public PrismObjectDefinition<ShadowType> getEditShadowDefinition(ResourceShadowDiscriminator discr,
            AuthorizationPhaseType phase, Task task, OperationResult parentResult)
            throws SchemaException, ConfigurationException, ObjectNotFoundException, ExpressionEvaluationException,
            CommunicationException, SecurityViolationException {
        // HACK hack hack
        // Make a dummy shadow instance here and evaluate the schema for that. It is not 100% correct. But good enough for now.
        // TODO: refactor when we add better support for multi-tenancy

        PrismObject<ShadowType> shadow = prismContext.createObject(ShadowType.class);
        ShadowType shadowType = shadow.asObjectable();
        ObjectReferenceType resourceRef = new ObjectReferenceType();
        if (discr != null) {
            resourceRef.setOid(discr.getResourceOid());
            shadowType.setResourceRef(resourceRef);
            shadowType.setKind(discr.getKind());
            shadowType.setIntent(discr.getIntent());
            shadowType.setObjectClass(discr.getObjectClass());
        }

        return getEditObjectDefinition(shadow, phase, task, parentResult);
    }

    @Override
    public RefinedObjectClassDefinition getEditObjectClassDefinition(PrismObject<ShadowType> shadow,
            PrismObject<ResourceType> resource, AuthorizationPhaseType phase, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        Validate.notNull(resource, "Resource must not be null");

        RefinedResourceSchema refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource);
        CompositeRefinedObjectClassDefinition rocd = refinedSchema.determineCompositeObjectClassDefinition(shadow);
        if (rocd == null) {
            LOGGER.debug("No object class definition for shadow {}, returning null", shadow.getOid());
            return null;
        }
        LayerRefinedObjectClassDefinition layeredROCD = rocd.forLayer(LayerType.PRESENTATION);

        // TODO: maybe we need to expose owner resolver in the interface?
        ObjectSecurityConstraints securityConstraints = securityEnforcer.compileSecurityConstraints(shadow, null,
                task, result);
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Security constrains for {}:\n{}", shadow,
                    securityConstraints == null ? "null" : securityConstraints.debugDump());
        }
        if (securityConstraints == null) {
            return null;
        }

        ItemPath attributesPath = SchemaConstants.PATH_ATTRIBUTES;
        AuthorizationDecisionType attributesReadDecision = schemaTransformer.computeItemDecision(
                securityConstraints, attributesPath, ModelAuthorizationAction.READ.getUrl(),
                securityConstraints.getActionDecision(ModelAuthorizationAction.READ.getUrl(), phase), phase);
        AuthorizationDecisionType attributesAddDecision = schemaTransformer.computeItemDecision(securityConstraints,
                attributesPath, ModelAuthorizationAction.ADD.getUrl(),
                securityConstraints.getActionDecision(ModelAuthorizationAction.ADD.getUrl(), phase), phase);
        AuthorizationDecisionType attributesModifyDecision = schemaTransformer.computeItemDecision(
                securityConstraints, attributesPath, ModelAuthorizationAction.MODIFY.getUrl(),
                securityConstraints.getActionDecision(ModelAuthorizationAction.MODIFY.getUrl(), phase), phase);
        LOGGER.trace("Attributes container access read:{}, add:{}, modify:{}", attributesReadDecision,
                attributesAddDecision, attributesModifyDecision);

        /*
         *  We are going to modify attribute definitions list.
         *  So let's make a (shallow) clone here, although it is probably not strictly necessary.
         */
        layeredROCD = layeredROCD.clone();
        for (LayerRefinedAttributeDefinition rAttrDef : layeredROCD.getAttributeDefinitions()) {
            ItemPath attributePath = new ItemPath(ShadowType.F_ATTRIBUTES, rAttrDef.getName());
            AuthorizationDecisionType attributeReadDecision = schemaTransformer.computeItemDecision(
                    securityConstraints, attributePath, ModelAuthorizationAction.READ.getUrl(),
                    attributesReadDecision, phase);
            AuthorizationDecisionType attributeAddDecision = schemaTransformer.computeItemDecision(
                    securityConstraints, attributePath, ModelAuthorizationAction.ADD.getUrl(),
                    attributesAddDecision, phase);
            AuthorizationDecisionType attributeModifyDecision = schemaTransformer.computeItemDecision(
                    securityConstraints, attributePath, ModelAuthorizationAction.MODIFY.getUrl(),
                    attributesModifyDecision, phase);
            LOGGER.trace("Attribute {} access read:{}, add:{}, modify:{}", rAttrDef.getName(),
                    attributeReadDecision, attributeAddDecision, attributeModifyDecision);
            if (attributeReadDecision != AuthorizationDecisionType.ALLOW) {
                ((LayerRefinedAttributeDefinitionImpl) rAttrDef).setOverrideCanRead(false);
            }
            if (attributeAddDecision != AuthorizationDecisionType.ALLOW) {
                ((LayerRefinedAttributeDefinitionImpl) rAttrDef).setOverrideCanAdd(false);
            }
            if (attributeModifyDecision != AuthorizationDecisionType.ALLOW) {
                ((LayerRefinedAttributeDefinitionImpl) rAttrDef).setOverrideCanModify(false);
            }
        }

        // TODO what about activation and credentials?

        return layeredROCD;
    }

    public <O extends ObjectType, R extends AbstractRoleType> ItemSecurityConstraints getAllowedRequestAssignmentItems(
            PrismObject<O> object, PrismObject<R> target, Task task, OperationResult result)
            throws SchemaException, SecurityViolationException, ObjectNotFoundException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException {
        return securityEnforcer.getAllowedRequestAssignmentItems(securityContextManager.getPrincipal(),
                ModelAuthorizationAction.ASSIGN.getUrl(), object, target, null, task, result);
    }

    @Override
    public Collection<? extends DisplayableValue<String>> getActionUrls() {
        return Arrays.asList(ModelAuthorizationAction.values());
    }

    @Override
    public <F extends FocusType> RoleSelectionSpecification getAssignableRoleSpecification(PrismObject<F> focus,
            Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException,
            CommunicationException, SecurityViolationException {
        OperationResult result = parentResult.createMinorSubresult(GET_ASSIGNABLE_ROLE_SPECIFICATION);

        RoleSelectionSpecification spec = new RoleSelectionSpecification();

        ObjectSecurityConstraints securityConstraints;
        try {
            securityConstraints = securityEnforcer.compileSecurityConstraints(focus, null, task, result);
        } catch (ExpressionEvaluationException | ObjectNotFoundException | SchemaException | CommunicationException
                | SecurityViolationException e) {
            result.recordFatalError(e);
            throw e;
        }
        if (securityConstraints == null) {
            return null;
        }
        AuthorizationDecisionType decision = securityConstraints.findItemDecision(SchemaConstants.PATH_ASSIGNMENT,
                ModelAuthorizationAction.MODIFY.getUrl(), AuthorizationPhaseType.REQUEST);
        if (decision == AuthorizationDecisionType.ALLOW) {
            getAllRoleTypesSpec(spec, result);
            result.recordSuccess();
            return spec;
        }
        if (decision == AuthorizationDecisionType.DENY) {
            result.recordSuccess();
            spec.setNoRoleTypes();
            spec.setFilter(NoneFilter.createNone());
            return spec;
        }
        decision = securityConstraints.getActionDecision(ModelAuthorizationAction.MODIFY.getUrl(),
                AuthorizationPhaseType.REQUEST);
        if (decision == AuthorizationDecisionType.ALLOW) {
            getAllRoleTypesSpec(spec, result);
            result.recordSuccess();
            return spec;
        }
        if (decision == AuthorizationDecisionType.DENY) {
            result.recordSuccess();
            spec.setNoRoleTypes();
            spec.setFilter(NoneFilter.createNone());
            return spec;
        }

        try {
            ObjectFilter filter = securityEnforcer.preProcessObjectFilter(ModelAuthorizationAction.ASSIGN.getUrl(),
                    AuthorizationPhaseType.REQUEST, AbstractRoleType.class, focus, AllFilter.createAll(), null,
                    task, result);
            LOGGER.trace("assignableRoleSpec filter: {}", filter);
            spec.setFilter(filter);
            if (filter instanceof NoneFilter) {
                result.recordSuccess();
                spec.setNoRoleTypes();
                return spec;
            } else if (filter == null || filter instanceof AllFilter) {
                getAllRoleTypesSpec(spec, result);
                result.recordSuccess();
                return spec;
            } else if (filter instanceof OrFilter) {
                Collection<RoleSelectionSpecEntry> allRoleTypeDvals = new ArrayList<>();
                for (ObjectFilter subfilter : ((OrFilter) filter).getConditions()) {
                    Collection<RoleSelectionSpecEntry> roleTypeDvals = getRoleSelectionSpecEntries(subfilter);
                    if (roleTypeDvals == null || roleTypeDvals.isEmpty()) {
                        // This branch of the OR clause does not have any constraint for roleType
                        // therefore all role types are possible (regardless of other branches, this is OR)
                        spec = new RoleSelectionSpecification();
                        spec.setFilter(filter);
                        getAllRoleTypesSpec(spec, result);
                        result.recordSuccess();
                        return spec;
                    } else {
                        allRoleTypeDvals.addAll(roleTypeDvals);
                    }
                }
                addRoleTypeSpecEntries(spec, allRoleTypeDvals, result);
            } else {
                Collection<RoleSelectionSpecEntry> roleTypeDvals = getRoleSelectionSpecEntries(filter);
                if (roleTypeDvals == null || roleTypeDvals.isEmpty()) {
                    getAllRoleTypesSpec(spec, result);
                    result.recordSuccess();
                    return spec;
                } else {
                    addRoleTypeSpecEntries(spec, roleTypeDvals, result);
                }
            }
            result.recordSuccess();
            return spec;
        } catch (SchemaException | ConfigurationException | ObjectNotFoundException
                | ExpressionEvaluationException e) {
            result.recordFatalError(e);
            throw e;
        }
    }

    private void addRoleTypeSpecEntries(RoleSelectionSpecification spec,
            Collection<RoleSelectionSpecEntry> roleTypeDvals, OperationResult result)
            throws ObjectNotFoundException, SchemaException, ConfigurationException {
        if (roleTypeDvals == null || roleTypeDvals.isEmpty()) {
            getAllRoleTypesSpec(spec, result);
        } else {
            if (RoleSelectionSpecEntry.hasNegative(roleTypeDvals)) {
                Collection<RoleSelectionSpecEntry> positiveList = RoleSelectionSpecEntry.getPositive(roleTypeDvals);
                if (positiveList == null || positiveList.isEmpty()) {
                    positiveList = getRoleSpecEntriesForAllRoles(result);
                }
                if (positiveList == null || positiveList.isEmpty()) {
                    return;
                }
                for (RoleSelectionSpecEntry positiveEntry : positiveList) {
                    if (!RoleSelectionSpecEntry.hasNegativeValue(roleTypeDvals, positiveEntry.getValue())) {
                        spec.addRoleType(positiveEntry);
                    }
                }

            } else {
                spec.addRoleTypes(roleTypeDvals);
            }
        }
    }

    private RoleSelectionSpecification getAllRoleTypesSpec(RoleSelectionSpecification spec, OperationResult result)
            throws ObjectNotFoundException, SchemaException, ConfigurationException {
        Collection<RoleSelectionSpecEntry> allEntries = getRoleSpecEntriesForAllRoles(result);
        if (allEntries == null || allEntries.isEmpty()) {
            return spec;
        }
        spec.addRoleTypes(allEntries);
        return spec;
    }

    private Collection<RoleSelectionSpecEntry> getRoleSpecEntriesForAllRoles(OperationResult result)
            throws ObjectNotFoundException, SchemaException, ConfigurationException {
        ObjectTemplateType objectTemplateType = schemaTransformer.determineObjectTemplate(RoleType.class,
                AuthorizationPhaseType.REQUEST, result);
        if (objectTemplateType == null) {
            return null;
        }
        Collection<RoleSelectionSpecEntry> allEntries = new ArrayList();
        for (ObjectTemplateItemDefinitionType itemDef : objectTemplateType.getItem()) {
            ItemPathType ref = itemDef.getRef();
            if (ref == null) {
                continue;
            }
            ItemPath itemPath = ref.getItemPath();
            QName itemName = ItemPath.getName(itemPath.first());
            if (itemName == null) {
                continue;
            }
            if (QNameUtil.match(RoleType.F_ROLE_TYPE, itemName)) {
                ObjectReferenceType valueEnumerationRef = itemDef.getValueEnumerationRef();
                if (valueEnumerationRef == null || valueEnumerationRef.getOid() == null) {
                    return allEntries;
                }
                Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(
                        LookupTableType.F_ROW, GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE));
                PrismObject<LookupTableType> lookup = cacheRepositoryService.getObject(LookupTableType.class,
                        valueEnumerationRef.getOid(), options, result);
                for (LookupTableRowType row : lookup.asObjectable().getRow()) {
                    PolyStringType polyLabel = row.getLabel();
                    String key = row.getKey();
                    String label = key;
                    if (polyLabel != null) {
                        label = polyLabel.getOrig();
                    }
                    RoleSelectionSpecEntry roleTypeDval = new RoleSelectionSpecEntry(key, label, null);
                    allEntries.add(roleTypeDval);
                }
                return allEntries;
            }
        }
        return allEntries;
    }

    private Collection<RoleSelectionSpecEntry> getRoleSelectionSpecEntries(ObjectFilter filter)
            throws SchemaException {
        LOGGER.trace("getRoleSelectionSpec({})", filter);
        if (filter == null || filter instanceof AllFilter) {
            return null;
        } else if (filter instanceof EqualFilter<?>) {
            return createSingleDisplayableValueCollection(getRoleSelectionSpecEq((EqualFilter) filter));
        } else if (filter instanceof AndFilter) {
            for (ObjectFilter subfilter : ((AndFilter) filter).getConditions()) {
                if (subfilter instanceof EqualFilter<?>) {
                    RoleSelectionSpecEntry roleTypeDval = getRoleSelectionSpecEq((EqualFilter) subfilter);
                    if (roleTypeDval != null) {
                        return createSingleDisplayableValueCollection(roleTypeDval);
                    }
                }
            }
            return null;
        } else if (filter instanceof OrFilter) {
            Collection<RoleSelectionSpecEntry> col = new ArrayList<>(((OrFilter) filter).getConditions().size());
            for (ObjectFilter subfilter : ((OrFilter) filter).getConditions()) {
                if (subfilter instanceof EqualFilter<?>) {
                    RoleSelectionSpecEntry roleTypeDval = getRoleSelectionSpecEq((EqualFilter) subfilter);
                    if (roleTypeDval != null) {
                        col.add(roleTypeDval);
                    }
                }
            }
            return col;
        } else if (filter instanceof TypeFilter) {
            return getRoleSelectionSpecEntries(((TypeFilter) filter).getFilter());
        } else if (filter instanceof NotFilter) {
            Collection<RoleSelectionSpecEntry> subRoleSelectionSpec = getRoleSelectionSpecEntries(
                    ((NotFilter) filter).getFilter());
            RoleSelectionSpecEntry.negate(subRoleSelectionSpec);
            return subRoleSelectionSpec;
        } else if (filter instanceof RefFilter) {
            return null;
        } else {
            throw new UnsupportedOperationException("Unexpected filter " + filter);
        }
    }

    private Collection<RoleSelectionSpecEntry> createSingleDisplayableValueCollection(RoleSelectionSpecEntry dval) {
        if (dval == null) {
            return null;
        }
        Collection<RoleSelectionSpecEntry> col = new ArrayList<>(1);
        col.add(dval);
        return col;
    }

    private RoleSelectionSpecEntry getRoleSelectionSpecEq(EqualFilter<String> eqFilter) throws SchemaException {
        if (QNameUtil.match(RoleType.F_ROLE_TYPE, eqFilter.getElementName())
                || QNameUtil.match(RoleType.F_SUBTYPE, eqFilter.getElementName())) {
            List<PrismPropertyValue<String>> ppvs = eqFilter.getValues();
            if (ppvs.size() > 1) {
                throw new SchemaException("More than one value in roleType search filter");
            }
            String roleType = ppvs.get(0).getValue();
            RoleSelectionSpecEntry roleTypeDval = new RoleSelectionSpecEntry(roleType, roleType, null);
            return roleTypeDval;
        }
        return null;
    }

    @Override
    public <T extends ObjectType> ObjectFilter getDonorFilter(Class<T> searchResultType, ObjectFilter origFilter,
            String targetAuthorizationAction, Task task, OperationResult parentResult)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        return securityEnforcer.preProcessObjectFilter(ModelAuthorizationAction.ATTORNEY.getUrl(), null,
                searchResultType, null, origFilter, targetAuthorizationAction, task, parentResult);
    }

    @Override
    public <T extends ObjectType, O extends ObjectType> boolean canSearch(Class<T> resultType, Class<O> objectType,
            String objectOid, boolean includeSpecial, ObjectQuery query, Task task, OperationResult result)
            throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException,
            SecurityViolationException, ExpressionEvaluationException {
        PrismObject<O> object = null;
        if (objectOid != null) {
            object = (PrismObject<O>) objectResolver.getObject(objectType, objectOid, null, task, result)
                    .asPrismObject();
        }
        return securityEnforcer.canSearch(ModelAuthorizationAction.READ.getUrl(), null, resultType, object,
                includeSpecial, query.getFilter(), task, result);
    }

    @Override
    public AuthenticationsPolicyType getAuthenticationPolicy(PrismObject<UserType> user, Task task,
            OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
        // TODO: check for user membership in an organization (later versions)

        OperationResult result = parentResult.createMinorSubresult(GET_AUTHENTICATIONS_POLICY);
        return resolvePolicyTypeFromSecurityPolicy(AuthenticationsPolicyType.class,
                SecurityPolicyType.F_AUTHENTICATION, user, task, result);

    }

    @Override
    public RegistrationsPolicyType getRegistrationPolicy(PrismObject<UserType> user, Task task,
            OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
        // TODO: check for user membership in an organization (later versions)

        OperationResult result = parentResult.createMinorSubresult(GET_REGISTRATIONS_POLICY);
        return resolvePolicyTypeFromSecurityPolicy(RegistrationsPolicyType.class, SecurityPolicyType.F_REGISTRATION,
                user, task, result);
    }

    @Override
    public CredentialsPolicyType getCredentialsPolicy(PrismObject<UserType> user, Task task,
            OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
        // TODO: check for user membership in an organization (later versions)

        OperationResult result = parentResult.createMinorSubresult(GET_CREDENTIALS_POLICY);
        return resolvePolicyTypeFromSecurityPolicy(CredentialsPolicyType.class, SecurityPolicyType.F_CREDENTIALS,
                user, task, result);

    }

    private <C extends Containerable> C resolvePolicyTypeFromSecurityPolicy(Class<C> type, QName path,
            PrismObject<UserType> user, Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {

        SecurityPolicyType securityPolicyType = getSecurityPolicy(user, task, parentResult);
        if (securityPolicyType == null) {
            return null;
        }
        PrismContainer<C> container = securityPolicyType.asPrismObject().findContainer(path);
        if (container == null) {
            return null;
        }
        PrismContainerValue<C> containerValue = container.getValue();
        parentResult.recordSuccess();
        return containerValue.asContainerable();
    }

    @Override
    public SecurityPolicyType getSecurityPolicy(PrismObject<UserType> user, Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {
        OperationResult result = parentResult.createMinorSubresult(GET_SECURITY_POLICY);
        try {
            PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache
                    .getSystemConfiguration(result);
            if (systemConfiguration == null) {
                result.recordNotApplicableIfUnknown();
                return null;
            }

            SecurityPolicyType securityPolicyType = securityHelper.locateSecurityPolicy(user, systemConfiguration,
                    task, result);
            if (securityPolicyType == null) {
                result.recordNotApplicableIfUnknown();
                return null;
            }

            return securityPolicyType;
        } catch (SchemaException e) {
            result.recordFatalError(e);
            throw e;
        }
    }

    @NotNull
    @Override
    public AdminGuiConfigurationType getAdminGuiConfiguration(Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {
        AdminGuiConfigurationType config = getAdminGuiConfigurationInternal(task, parentResult);
        return config != null ? config : new AdminGuiConfigurationType();
    }

    public AdminGuiConfigurationType getAdminGuiConfigurationInternal(Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {
        MidPointPrincipal principal = null;
        try {
            principal = securityContextManager.getPrincipal();
        } catch (SecurityViolationException e) {
            LOGGER.warn("Security violation while getting principlal to get GUI config: {}", e.getMessage(), e);
        }

        if (principal == null) {
            PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache
                    .getSystemConfiguration(parentResult);
            if (systemConfiguration == null) {
                return null;
            }
            return systemConfiguration.asObjectable().getAdminGuiConfiguration();
        } else {
            return principal.getAdminGuiConfiguration();
        }
    }

    @Override
    public SystemConfigurationType getSystemConfiguration(OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {
        PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache
                .getSystemConfiguration(parentResult);
        if (systemConfiguration == null) {
            return null;
        }
        return systemConfiguration.asObjectable();
    }

    @Override
    public DeploymentInformationType getDeploymentInformationConfiguration(OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {
        PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache
                .getSystemConfiguration(parentResult);
        if (systemConfiguration == null) {
            return null;
        }
        return systemConfiguration.asObjectable().getDeploymentInformation();
    }

    @Override
    public AccessCertificationConfigurationType getCertificationConfiguration(OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException {
        PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache
                .getSystemConfiguration(parentResult);
        if (systemConfiguration == null) {
            return null;
        }
        return systemConfiguration.asObjectable().getAccessCertification();
    }

    @Override
    public boolean checkPassword(String userOid, ProtectedStringType password, Task task,
            OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
        OperationResult result = parentResult.createMinorSubresult(CHECK_PASSWORD);
        UserType userType;
        try {
            userType = objectResolver.getObjectSimple(UserType.class, userOid, null, task, result);
        } catch (ObjectNotFoundException e) {
            result.recordFatalError(e);
            throw e;
        }
        if (userType.getCredentials() == null || userType.getCredentials().getPassword() == null
                || userType.getCredentials().getPassword().getValue() == null) {
            return password == null;
        }
        ProtectedStringType currentPassword = userType.getCredentials().getPassword().getValue();
        boolean cmp;
        try {
            cmp = protector.compare(password, currentPassword);
        } catch (EncryptionException e) {
            result.recordFatalError(e);
            throw new SystemException(e.getMessage(), e);
        }
        result.recordSuccess();
        return cmp;
    }

    @Override
    public List<? extends Scene> visualizeDeltas(List<ObjectDelta<? extends ObjectType>> deltas, Task task,
            OperationResult result) throws SchemaException, ExpressionEvaluationException {
        return visualizer.visualizeDeltas(deltas, task, result);
    }

    @Override
    @NotNull
    public Scene visualizeDelta(ObjectDelta<? extends ObjectType> delta, Task task, OperationResult result)
            throws SchemaException, ExpressionEvaluationException {
        return visualizer.visualizeDelta(delta, task, result);
    }

    @Override
    public List<ConnectorOperationalStatus> getConnectorOperationalStatus(String resourceOid, Task task,
            OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, ExpressionEvaluationException {
        OperationResult result = parentResult.createMinorSubresult(GET_CONNECTOR_OPERATIONAL_STATUS);
        List<ConnectorOperationalStatus> status;
        try {
            status = provisioning.getConnectorOperationalStatus(resourceOid, task, result);
        } catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException
                | ExpressionEvaluationException e) {
            result.recordFatalError(e);
            throw e;
        }
        result.computeStatus();
        return status;
    }

    @Override
    public <O extends ObjectType> MergeDeltas<O> mergeObjectsPreviewDeltas(Class<O> type, String leftOid,
            String rightOid, String mergeConfigurationName, Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException,
            CommunicationException, SecurityViolationException {
        OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_DELTA);

        try {

            MergeDeltas<O> mergeDeltas = objectMerger.computeMergeDeltas(type, leftOid, rightOid,
                    mergeConfigurationName, task, result);

            result.computeStatus();
            return mergeDeltas;

        } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException
                | CommunicationException | SecurityViolationException | RuntimeException | Error e) {
            result.recordFatalError(e);
            throw e;
        }
    }

    @Override
    public <O extends ObjectType> PrismObject<O> mergeObjectsPreviewObject(Class<O> type, String leftOid,
            String rightOid, String mergeConfigurationName, Task task, OperationResult parentResult)
            throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException,
            CommunicationException, SecurityViolationException {
        OperationResult result = parentResult.createMinorSubresult(MERGE_OBJECTS_PREVIEW_OBJECT);

        try {

            MergeDeltas<O> mergeDeltas = objectMerger.computeMergeDeltas(type, leftOid, rightOid,
                    mergeConfigurationName, task, result);

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Merge preview {} + {} deltas:\n{}", leftOid, rightOid, mergeDeltas.debugDump(1));
            }

            final PrismObject<O> objectLeft = (PrismObject<O>) objectResolver
                    .getObjectSimple(type, leftOid, null, task, result).asPrismObject();

            if (mergeDeltas == null) {
                result.computeStatus();
                return objectLeft;
            }

            mergeDeltas.getLeftObjectDelta().applyTo(objectLeft);
            mergeDeltas.getLeftLinkDelta().applyTo(objectLeft);

            result.computeStatus();
            return objectLeft;

        } catch (ObjectNotFoundException | SchemaException | ConfigurationException | ExpressionEvaluationException
                | CommunicationException | SecurityViolationException | RuntimeException | Error e) {
            result.recordFatalError(e);
            throw e;
        }
    }

    @Override
    public <O extends ObjectType> String generateValue(ValuePolicyType policy, int defaultLength,
            boolean generateMinimalSize, PrismObject<O> object, String shortDesc, Task task,
            OperationResult parentResult) throws ExpressionEvaluationException, SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException {
        return policyProcessor.generate(null, policy, defaultLength, generateMinimalSize,
                createOriginResolver(object, parentResult), shortDesc, task, parentResult);
    }

    @Override
    public <O extends ObjectType> void generateValue(PrismObject<O> object,
            PolicyItemsDefinitionType policyItemsDefinition, Task task, OperationResult parentResult)
            throws ObjectAlreadyExistsException, ExpressionEvaluationException, SchemaException,
            ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException,
            PolicyViolationException {

        OperationResult result = parentResult.createSubresult(OPERATION_GENERATE_VALUE);

        ValuePolicyType valuePolicy = null;
        try {
            valuePolicy = getValuePolicy(object, task, result);
        } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException
                | SecurityViolationException | ExpressionEvaluationException e) {
            LOGGER.error("Failed to get value policy for generating value. ", e);
            result.recordFatalError("Error while getting value policy. Reason: " + e.getMessage(), e);
            throw e;
        }

        Collection<PropertyDelta<?>> deltasToExecute = new ArrayList<>();
        for (PolicyItemDefinitionType policyItemDefinition : policyItemsDefinition.getPolicyItemDefinition()) {
            OperationResult generateValueResult = parentResult.createSubresult(OPERATION_GENERATE_VALUE);

            LOGGER.trace("Default value policy: {}", valuePolicy);
            try {
                generateValue(object, valuePolicy, policyItemDefinition, task, generateValueResult);
            } catch (ExpressionEvaluationException | SchemaException | ObjectNotFoundException
                    | CommunicationException | ConfigurationException | SecurityViolationException e) {
                LOGGER.error("Failed to generate value for {} ", policyItemDefinition, e);
                generateValueResult.recordFatalError(
                        "Failed to generate value for " + policyItemDefinition + ". Reason: " + e.getMessage(), e);
                policyItemDefinition.setResult(generateValueResult.createOperationResultType());
                continue;
            }

            //TODO: not sure about the bulk actions here
            ItemPath path = getPath(policyItemDefinition);
            if (path == null) {
                if (isExecute(policyItemDefinition)) {
                    LOGGER.error(
                            "No item path defined in the target for policy item definition. Cannot generate value");
                    generateValueResult.recordFatalError(
                            "No item path defined in the target for policy item definition. Cannot generate value");
                    continue;
                }
            }

            PrismPropertyDefinition<?> propertyDef = null;
            if (path != null) {
                result.addArbitraryObjectAsParam("policyItemPath", path);

                propertyDef = getItemDefinition(object, path);
                if (propertyDef == null) {
                    if (isExecute(policyItemDefinition)) {
                        LOGGER.error(
                                "No definition for property {} in object. Is the path referencing prism property?"
                                        + path,
                                object);
                        generateValueResult.recordFatalError("No definition for property " + path + " in object "
                                + object + ". Is the path referencing prism property?");
                        continue;
                    }

                }
            }
            // end of not sure

            collectDeltasForGeneratedValuesIfNeeded(object, policyItemDefinition, deltasToExecute, path,
                    propertyDef, generateValueResult);
            generateValueResult.computeStatusIfUnknown();
        }
        result.computeStatus();
        if (!result.isAcceptable()) {
            return;
        }
        try {
            if (!deltasToExecute.isEmpty()) {
                if (object == null) {
                    LOGGER.error("Cannot execute changes for generated values, no object specified in request.");
                    result.recordFatalError(
                            "Cannot execute changes for generated values, no object specified in request.");
                    throw new SchemaException(
                            "Cannot execute changes for generated values, no object specified in request.");
                }
                String oid = object.getOid();
                Class<O> clazz = (Class<O>) object.asObjectable().getClass();
                modelCrudService.modifyObject(clazz, oid, deltasToExecute, null, task, result);

            }
        } catch (ObjectNotFoundException | SchemaException | ExpressionEvaluationException | CommunicationException
                | ConfigurationException | ObjectAlreadyExistsException | PolicyViolationException
                | SecurityViolationException e) {
            LOGGER.error("Could not execute deltas for generated values. Reason: " + e.getMessage(), e);
            result.recordFatalError("Could not execute deltas for gegenerated values. Reason: " + e.getMessage(),
                    e);
            throw e;
        }

    }

    private boolean isExecute(PolicyItemDefinitionType policyItemDefinition) {
        if (policyItemDefinition.isExecute() == null) {
            return false;
        }

        return policyItemDefinition.isExecute().booleanValue();
    }

    private ItemPath getPath(PolicyItemDefinitionType policyItemDefinition) {
        PolicyItemTargetType target = policyItemDefinition.getTarget();

        if (target == null) {
            return null;
        }

        ItemPathType itemPathType = target.getPath();
        if (itemPathType == null) {

            return null;
        }
        return itemPathType.getItemPath();
    }

    private <O extends ObjectType> PrismPropertyDefinition<?> getItemDefinition(PrismObject<O> object,
            ItemPath path) {
        ItemDefinition<?> itemDef = object.getDefinition().findItemDefinition(path);
        if (itemDef == null) {
            return null;
        } else if (!(itemDef instanceof PrismPropertyDefinition)) {
            return null;
        }

        return (PrismPropertyDefinition<?>) itemDef;
    }

    private <O extends ObjectType> void collectDeltasForGeneratedValuesIfNeeded(PrismObject<O> object,
            PolicyItemDefinitionType policyItemDefinition, Collection<PropertyDelta<?>> deltasToExecute,
            ItemPath path, PrismPropertyDefinition<?> itemDef, OperationResult result) throws SchemaException {

        Object value = policyItemDefinition.getValue();

        if (itemDef != null) {
            if (ProtectedStringType.COMPLEX_TYPE.equals(itemDef.getTypeName())) {
                ProtectedStringType pst = new ProtectedStringType();
                pst.setClearValue((String) value);
                value = pst;
            } else if (PolyStringType.COMPLEX_TYPE.equals(itemDef.getTypeName())) {
                value = new PolyString((String) value);
            }
        }
        if (object == null && isExecute(policyItemDefinition)) {
            LOGGER.warn(
                    "Cannot apply generated changes and cannot execute them becasue there is no target object specified.");
            result.recordFatalError(
                    "Cannot apply generated changes and cannot execute them becasue there is no target object specified.");
            return;
        }
        if (object != null) {
            PropertyDelta<?> propertyDelta = PropertyDelta.createModificationReplaceProperty(path,
                    object.getDefinition(), value);
            propertyDelta.applyTo(object); // in bulk actions we need to modify original objects - hope that REST is OK with this
            if (BooleanUtils.isTrue(policyItemDefinition.isExecute())) {
                deltasToExecute.add(propertyDelta);
            }
        }

    }

    private <O extends ObjectType> void generateValue(PrismObject<O> object, ValuePolicyType defaultPolicy,
            PolicyItemDefinitionType policyItemDefinition, Task task, OperationResult result)
            throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException {

        PolicyItemTargetType target = policyItemDefinition.getTarget();
        if ((target == null || ItemPath.isNullOrEmpty(target.getPath())) && isExecute(policyItemDefinition)) {
            LOGGER.error("Target item path must be defined");
            throw new SchemaException("Target item path must be defined");
        }
        ItemPath targetPath = null;

        if (target != null) {
            targetPath = target.getPath().getItemPath();
        }

        ValuePolicyType valuePolicy = resolveValuePolicy(policyItemDefinition, defaultPolicy, task, result);
        LOGGER.trace("Value policy used for generating new value : {}", valuePolicy);
        StringPolicyType stringPolicy = valuePolicy != null ? valuePolicy.getStringPolicy() : null;
        if (stringPolicy == null) {
            LOGGER.trace("No sting policy defined. Cannot generate value.");
            result.recordFatalError("No string policy defined. Cannot generate value");
            return;
            //         throw new SchemaException("No value policy for " + targetPath);
        }

        String newValue = policyProcessor.generate(targetPath, valuePolicy, 10, false,
                createOriginResolver(object, result), "generating value for" + targetPath, task, result);
        policyItemDefinition.setValue(newValue);
    }

    private ValuePolicyType resolveValuePolicy(PolicyItemDefinitionType policyItemDefinition,
            ValuePolicyType defaultPolicy, Task task, OperationResult result)
            throws ObjectNotFoundException, SchemaException {
        if (policyItemDefinition.getValuePolicyRef() != null) {
            LOGGER.trace("Trying to resolve value policy {} for policy item definition", policyItemDefinition);
            return objectResolver.resolve(policyItemDefinition.getValuePolicyRef(), ValuePolicyType.class, null,
                    "valuePolicyRef in policyItemDefinition", task, result);
        }

        return defaultPolicy;
    }

    public <O extends ObjectType> void validateValue(PrismObject<O> object,
            PolicyItemsDefinitionType policyItemsDefinition, Task task, OperationResult parentResult)
            throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException, PolicyViolationException {
        ValuePolicyType valuePolicy = getValuePolicy(object, task, parentResult);
        for (PolicyItemDefinitionType policyItemDefinition : policyItemsDefinition.getPolicyItemDefinition()) {
            validateValue(object, valuePolicy, policyItemDefinition, task, parentResult);
        }
    }

    private <O extends ObjectType> ValuePolicyType getValuePolicy(PrismObject<O> object, Task task,
            OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException,
            ConfigurationException, SecurityViolationException, ExpressionEvaluationException {
        // user-level policy
        CredentialsPolicyType policy = null;
        if (object != null && object.getCompileTimeClass().isAssignableFrom(UserType.class)) {
            policy = getCredentialsPolicy((PrismObject<UserType>) object, task, parentResult);
        }

        SystemConfigurationType systemConfigurationType = getSystemConfiguration(parentResult);
        if (!containsValuePolicyDefinition(policy)) {
            SecurityPolicyType securityPolicy = securityHelper.locateGlobalSecurityPolicy(systemConfigurationType,
                    task, parentResult);
            if (securityPolicy != null) {
                policy = securityPolicy.getCredentials();
            }
        }

        if (!containsValuePolicyDefinition(policy)) {
            SecurityPolicyType securityPolicy = securityHelper.locateGlobalPasswordPolicy(systemConfigurationType,
                    task, parentResult);
            if (securityPolicy != null) {
                policy = securityPolicy.getCredentials();
            }
        }

        if (containsValuePolicyDefinition(policy)) {

            if (policy.getPassword().getValuePolicyRef() != null) {
                return objectResolver.resolve(policy.getPassword().getValuePolicyRef(), ValuePolicyType.class, null,
                        "valuePolicyRef in password credential policy", task, parentResult);
            } else if (policy.getPassword().getPasswordPolicyRef() != null) {
                // DEPRECATED
                return objectResolver.resolve(policy.getPassword().getPasswordPolicyRef(), ValuePolicyType.class,
                        null, "valuePolicyRef in password credential policy", task, parentResult);
            }
        }

        return null;
    }

    private boolean containsValuePolicyDefinition(CredentialsPolicyType policy) {
        if (policy == null) {
            return false;
        }

        if (policy.getPassword() == null) {
            return false;
        }

        if (policy.getPassword().getValuePolicyRef() != null) {
            return true;
        }

        if (policy.getPassword().getPasswordPolicyRef() != null) {
            return true;
        }

        return false;
    }

    private <T, O extends ObjectType> boolean validateValue(PrismObject<O> object, ValuePolicyType policy,
            PolicyItemDefinitionType policyItemDefinition, Task task, OperationResult parentResult)
            throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException,
            ConfigurationException, SecurityViolationException, PolicyViolationException {

        ValuePolicyType stringPolicy = resolveValuePolicy(policyItemDefinition, policy, task, parentResult);

        Object value = policyItemDefinition.getValue();
        String valueToValidate = null;
        if (value instanceof RawType) {
            valueToValidate = ((RawType) value).getParsedRealValue(String.class);
        } else {
            valueToValidate = (String) value;
        }

        List<String> valuesToValidate = new ArrayList<>();
        PolicyItemTargetType target = policyItemDefinition.getTarget();
        ItemPath path = null;
        if (target != null) {
            path = target.getPath().getItemPath();
        }
        if (StringUtils.isNotEmpty(valueToValidate)) {
            valuesToValidate.add(valueToValidate);
        } else {
            if (target == null || target.getPath() == null) {
                LOGGER.error("Target item path must be defined");
                parentResult.recordFatalError("Target item path must be defined");
                throw new SchemaException("Target item path must be defined");
            }
            path = target.getPath().getItemPath();

            if (object == null) {
                LOGGER.error("Object which values should be validated is null. Nothing to validate.");
                parentResult
                        .recordFatalError("Object which values should be validated is null. Nothing to validate.");
                throw new SchemaException("Object which values should be validated is null. Nothing to validate.");
            }

            PrismProperty<T> property = object.findProperty(path);
            if (property == null || property.isEmpty()) {
                LOGGER.error("Attribute {} has no value. Nothing to validate.", property);
                parentResult.recordFatalError("Attribute " + property + " has no value. Nothing to validate");
                throw new SchemaException("Attribute " + property + " has no value. Nothing to validate");
            }

            PrismPropertyDefinition<T> itemToValidateDefinition = property.getDefinition();
            QName definitionName = itemToValidateDefinition.getTypeName();
            if (!isSupportedType(definitionName)) {
                LOGGER.error(
                        "Trying to validate string policy on the property of type {} failed. Unsupported type.",
                        itemToValidateDefinition);
                parentResult.recordFatalError("Trying to validate string policy on the property of type "
                        + itemToValidateDefinition + " failed. Unsupported type.");
                throw new SchemaException("Trying to validate string policy on the property of type "
                        + itemToValidateDefinition + " failed. Unsupported type.");
            }

            if (itemToValidateDefinition.isSingleValue()) {
                if (definitionName.equals(PolyStringType.COMPLEX_TYPE)) {
                    valueToValidate = ((PolyString) property.getRealValue()).getOrig();

                } else if (definitionName.equals(ProtectedStringType.COMPLEX_TYPE)) {
                    ProtectedStringType protectedString = ((ProtectedStringType) property.getRealValue());
                    valueToValidate = getClearValue(protectedString);

                } else {
                    valueToValidate = (String) property.getRealValue();
                }
                valuesToValidate.add(valueToValidate);
            } else {
                if (definitionName.equals(DOMUtil.XSD_STRING)) {
                    valuesToValidate.addAll(property.getRealValues(String.class));
                } else if (definitionName.equals(ProtectedStringType.COMPLEX_TYPE)) {
                    for (ProtectedStringType protectedString : property.getRealValues(ProtectedStringType.class)) {
                        valuesToValidate.add(getClearValue(protectedString));
                    }
                } else {
                    for (PolyString val : property.getRealValues(PolyString.class)) {
                        valuesToValidate.add(val.getOrig());
                    }
                }
            }

        }

        for (String newValue : valuesToValidate) {
            OperationResult result = parentResult.createSubresult(OPERATION_VALIDATE_VALUE + ".value");
            if (path != null)
                result.addParam("path", path.toString());
            result.addParam("valueToValidate", newValue);
            //         if (stringPolicy == null) {
            //            stringPolicy = new ValuePolicyType();
            //            stringPolicy.setName(PolyString.toPolyStringType(new PolyString("Default policy")));
            //         }

            ObjectValuePolicyEvaluator evaluator = new ObjectValuePolicyEvaluator();
            evaluator.setValuePolicy(stringPolicy);
            evaluator.setValuePolicyProcessor(policyProcessor);
            evaluator.setProtector(protector);
            evaluator.setValueItemPath(path);
            evaluator.setOriginResolver(getOriginResolver(object));
            evaluator.setTask(task);
            evaluator.setShortDesc(" rest validate ");
            if (object != null && path != null && path.isSuperPathOrEquivalent(SchemaConstants.PATH_PASSWORD)) {
                evaluator.setSecurityPolicy(getSecurityPolicy((PrismObject<UserType>) object, task, parentResult));
                PrismContainer<PasswordType> password = object.findContainer(SchemaConstants.PATH_PASSWORD);
                PasswordType passwordType = null;
                if (password != null) {
                    PrismContainerValue<PasswordType> passwordPcv = password.getValue();
                    passwordType = passwordPcv != null ? passwordPcv.asContainerable() : null;
                }
                evaluator.setOldCredentialType(passwordType);
            }
            evaluator.setNow(clock.currentTimeXMLGregorianCalendar());
            LOGGER.trace("Validating value started");
            OperationResult subResult = evaluator.validateStringValue(newValue);
            LOGGER.trace("Validating value finished");
            result.addSubresult(subResult);
            //         
            result.computeStatus();

            //         if (!policyProcessor.validateValue(newValue, stringPolicy, createOriginResolver(object, result), "validate value " + (path!= null ? "for " + path : "") + " for " + object + " value " + valueToValidate, task, result)) {
            //            result.recordFatalError("Validation for value " + newValue + " against policy " + stringPolicy + " failed");
            //            LOGGER.error("Validation for value {} against policy {} failed", newValue, stringPolicy);
            //         }

        }

        parentResult.computeStatus();
        policyItemDefinition.setResult(parentResult.createOperationResultType());

        return parentResult.isAcceptable();

    }

    private <O extends ObjectType> AbstractValuePolicyOriginResolver<O> getOriginResolver(PrismObject<O> object) {
        if (object != null && UserType.class.equals(object.getCompileTimeClass())) {
            new UserValuePolicyOriginResolver((PrismObject<UserType>) object, objectResolver);
        }

        //TODO not supported yet
        return null;
    }

    private <O extends ObjectType> AbstractValuePolicyOriginResolver<O> createOriginResolver(PrismObject<O> object,
            OperationResult result) throws SchemaException {
        if (object == null) {
            return null;
        }
        if (object.canRepresent(UserType.class)) {
            return (AbstractValuePolicyOriginResolver<O>) new UserValuePolicyOriginResolver(
                    (PrismObject<UserType>) object, objectResolver);
        }
        if (object.canRepresent(ShadowType.class)) {
            return (AbstractValuePolicyOriginResolver<O>) new ShadowValuePolicyOriginResolver(
                    (PrismObject<ShadowType>) object, objectResolver);
        }
        SchemaException e = new SchemaException("Unsupport object type " + object);
        result.recordFatalError(e);
        throw e;
    }

    private boolean isSupportedType(QName type) {

        if (QNameUtil.qNameToUri(type).equals(QNameUtil.qNameToUri(DOMUtil.XSD_STRING))) {
            return true;
        }

        if (QNameUtil.qNameToUri(type).equals(QNameUtil.qNameToUri(PolyStringType.COMPLEX_TYPE))) {
            return true;
        }

        if (QNameUtil.qNameToUri(type).equals(QNameUtil.qNameToUri(ProtectedStringType.COMPLEX_TYPE))) {
            return true;
        }

        return false;
    }

    private String getClearValue(ProtectedStringType protectedString)
            throws SchemaException, PolicyViolationException {
        try {
            if (protectedString.isEncrypted()) {

                return protector.decryptString(protectedString);

            } else if (protectedString.getClearValue() != null) {
                return protector.decryptString(protectedString);
            } else if (protectedString.isHashed()) {
                throw new SchemaException("Cannot validate value of hashed password");
            }
        } catch (EncryptionException e) {
            throw new PolicyViolationException(e.getMessage(), e);
        }
        return null;
    }

    // TODO TODO TODO deduplicate this somehow!

    @NotNull
    @Override
    public List<ObjectReferenceType> getDeputyAssignees(AbstractWorkItemType workItem, Task task,
            OperationResult parentResult) throws SchemaException {
        OperationResult result = parentResult.createMinorSubresult(GET_DEPUTY_ASSIGNEES);
        RepositoryCache.enter();
        try {
            Set<String> oidsToSkip = new HashSet<>();
            List<ObjectReferenceType> deputies = new ArrayList<>();
            workItem.getAssigneeRef().forEach(a -> oidsToSkip.add(a.getOid()));
            getDeputyAssignees(deputies, workItem, oidsToSkip, task, result);
            result.computeStatusIfUnknown();
            return deputies;
        } catch (Throwable t) {
            result.recordFatalError(t.getMessage(), t);
            throw t;
        } finally {
            RepositoryCache.exit();
        }
    }

    @NotNull
    @Override
    public List<ObjectReferenceType> getDeputyAssignees(ObjectReferenceType assigneeRef, QName limitationItemName,
            Task task, OperationResult parentResult) throws SchemaException {
        OperationResult result = parentResult.createMinorSubresult(GET_DEPUTY_ASSIGNEES);
        RepositoryCache.enter();
        try {
            Set<String> oidsToSkip = new HashSet<>();
            oidsToSkip.add(assigneeRef.getOid());
            List<ObjectReferenceType> deputies = new ArrayList<>();
            getDeputyAssigneesNoWorkItem(deputies, assigneeRef, limitationItemName, oidsToSkip, task, result);
            result.computeStatusIfUnknown();
            return deputies;
        } catch (Throwable t) {
            result.recordFatalError(t.getMessage(), t);
            throw t;
        } finally {
            RepositoryCache.exit();
        }
    }

    private void getDeputyAssignees(List<ObjectReferenceType> deputies, AbstractWorkItemType workItem,
            Set<String> oidsToSkip, Task task, OperationResult result) throws SchemaException {
        List<PrismReferenceValue> assigneeReferencesToQuery = workItem.getAssigneeRef().stream()
                .map(assigneeRef -> assigneeRef.clone().relation(PrismConstants.Q_ANY).asReferenceValue())
                .collect(Collectors.toList());
        ObjectQuery query = QueryBuilder.queryFor(UserType.class, prismContext).item(UserType.F_DELEGATED_REF)
                .ref(assigneeReferencesToQuery).build();
        SearchResultList<PrismObject<UserType>> potentialDeputies = cacheRepositoryService
                .searchObjects(UserType.class, query, null, result);
        for (PrismObject<UserType> potentialDeputy : potentialDeputies) {
            if (oidsToSkip.contains(potentialDeputy.getOid())) {
                continue;
            }
            if (determineDeputyValidity(potentialDeputy, workItem.getAssigneeRef(), workItem,
                    OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS, task, result)) {
                deputies.add(ObjectTypeUtil.createObjectRefWithFullObject(potentialDeputy));
                oidsToSkip.add(potentialDeputy.getOid());
            }
        }
    }

    private void getDeputyAssigneesNoWorkItem(List<ObjectReferenceType> deputies, ObjectReferenceType assigneeRef,
            QName limitationItemName, Set<String> oidsToSkip, Task task, OperationResult result)
            throws SchemaException {
        PrismReferenceValue assigneeReferenceToQuery = assigneeRef.clone().relation(PrismConstants.Q_ANY)
                .asReferenceValue();
        ObjectQuery query = QueryBuilder.queryFor(UserType.class, prismContext).item(UserType.F_DELEGATED_REF)
                .ref(assigneeReferenceToQuery).build();
        SearchResultList<PrismObject<UserType>> potentialDeputies = cacheRepositoryService
                .searchObjects(UserType.class, query, null, result);
        for (PrismObject<UserType> potentialDeputy : potentialDeputies) {
            if (oidsToSkip.contains(potentialDeputy.getOid())) {
                continue;
            }
            if (determineDeputyValidity(potentialDeputy, Collections.singletonList(assigneeRef), null,
                    limitationItemName, task, result)) {
                deputies.add(ObjectTypeUtil.createObjectRefWithFullObject(potentialDeputy));
                oidsToSkip.add(potentialDeputy.getOid());
            }
        }
    }

    private boolean determineDeputyValidity(PrismObject<UserType> potentialDeputy,
            List<ObjectReferenceType> assignees, @Nullable AbstractWorkItemType workItem,
            QName privilegeLimitationItemName, Task task, OperationResult result) {
        AssignmentEvaluator.Builder<UserType> builder = new AssignmentEvaluator.Builder<UserType>()
                .repository(cacheRepositoryService)
                .focusOdo(new ObjectDeltaObject<>(potentialDeputy, null, potentialDeputy)).channel(null)
                .objectResolver(objectResolver).systemObjectCache(systemObjectCache).prismContext(prismContext)
                .mappingFactory(mappingFactory).mappingEvaluator(mappingEvaluator)
                .activationComputer(activationComputer).now(clock.currentTimeXMLGregorianCalendar()).loginMode(true)
                // We do not have real lens context here. But the push methods in ModelExpressionThreadLocalHolder
                // will need something to push on the stack. So give them context placeholder.
                .lensContext(new LensContextPlaceholder<>(potentialDeputy, prismContext));
        AssignmentEvaluator<UserType> assignmentEvaluator = builder.build();

        for (AssignmentType assignmentType : potentialDeputy.asObjectable().getAssignment()) {
            if (!DeputyUtils.isDelegationAssignment(assignmentType)) {
                continue;
            }
            try {
                ItemDeltaItem<PrismContainerValue<AssignmentType>, PrismContainerDefinition<AssignmentType>> assignmentIdi = new ItemDeltaItem<>();
                assignmentIdi.setItemOld(LensUtil.createAssignmentSingleValueContainerClone(assignmentType));
                assignmentIdi.recompute();
                // TODO some special mode for verification of the validity - we don't need complete calculation here!
                EvaluatedAssignment<UserType> assignment = assignmentEvaluator.evaluate(assignmentIdi,
                        PlusMinusZero.ZERO, false, potentialDeputy.asObjectable(), potentialDeputy.toString(), task,
                        result);
                if (!assignment.isValid()) {
                    continue;
                }
                for (EvaluatedAssignmentTarget target : assignment.getRoles().getNonNegativeValues()) {
                    if (target.getTarget() != null && target.getTarget().getOid() != null
                            && DeputyUtils.isDelegationPath(target.getAssignmentPath())
                            && ObjectTypeUtil.containsOid(assignees, target.getTarget().getOid())) {
                        List<OtherPrivilegesLimitationType> limitations = DeputyUtils
                                .extractLimitations(target.getAssignmentPath());
                        if (workItem != null
                                && DeputyUtils.limitationsAllow(limitations, privilegeLimitationItemName, workItem)
                                || workItem == null
                                        && DeputyUtils.limitationsAllow(limitations, privilegeLimitationItemName)) {
                            return true;
                        }
                    }
                }
            } catch (CommonException e) {
                LoggingUtils.logUnexpectedException(LOGGER,
                        "Couldn't verify 'deputy' relation between {} and {} for work item {}; assignment: {}", e,
                        potentialDeputy, assignees, workItem, assignmentType);
            }
        }
        return false;
    }

    @Override
    public ActivationStatusType getEffectiveStatus(String lifecycleStatus, ActivationType activationType) {
        return activationComputer.getEffectiveStatus(lifecycleStatus, activationType);
    }

    @Override
    public MidPointPrincipal assumePowerOfAttorney(PrismObject<UserType> donor, Task task, OperationResult result)
            throws SchemaException, SecurityViolationException, ObjectNotFoundException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException {
        MidPointPrincipal attorneyPrincipal = securityContextManager.getPrincipal();
        MidPointPrincipal donorPrincipal = securityEnforcer.createDonorPrincipal(attorneyPrincipal,
                ModelAuthorizationAction.ATTORNEY.getUrl(), donor, task, result);

        // TODO: audit switch

        securityContextManager.setupPreAuthenticatedSecurityContext(donorPrincipal);

        return donorPrincipal;
    }

    @Override
    public MidPointPrincipal dropPowerOfAttorney(Task task, OperationResult result)
            throws SchemaException, SecurityViolationException, ObjectNotFoundException,
            ExpressionEvaluationException, CommunicationException, ConfigurationException {
        MidPointPrincipal donorPrincipal = securityContextManager.getPrincipal();
        if (donorPrincipal.getAttorney() == null) {
            throw new IllegalStateException(
                    "Attempt to drop attorney powers using non-donor principal " + donorPrincipal);
        }
        MidPointPrincipal previousPrincipal = donorPrincipal.getPreviousPrincipal();
        if (previousPrincipal == null) {
            throw new IllegalStateException(
                    "Attempt to drop attorney powers, but no previous principal in " + donorPrincipal);
        }

        // TODO: audit switch

        // TODO: maybe refresh previous principal using userProfileService?
        securityContextManager.setupPreAuthenticatedSecurityContext(previousPrincipal);

        return previousPrincipal;
    }

    @Override
    @NotNull
    public LocalizableMessageType createLocalizableMessageType(LocalizableMessageTemplateType template,
            Map<QName, Object> variables, Task task, OperationResult result)
            throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        ExpressionVariables vars = new ExpressionVariables();
        vars.addVariableDefinitions(variables);
        return LensUtil.interpretLocalizableMessageTemplate(template, vars, expressionFactory, prismContext, task,
                result);
    }

    @Override
    public ExecuteCredentialResetResponseType executeCredentialsReset(PrismObject<UserType> user,
            ExecuteCredentialResetRequestType executeCredentialResetRequest, Task task,
            OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException,
            ConfigurationException, SecurityViolationException, ExpressionEvaluationException,
            ObjectAlreadyExistsException, PolicyViolationException {
        LocalizableMessageBuilder builder = new LocalizableMessageBuilder();

        ExecuteCredentialResetResponseType response = new ExecuteCredentialResetResponseType(prismContext);

        String resetMethod = executeCredentialResetRequest.getResetMethod();
        if (StringUtils.isBlank(resetMethod)) {
            LocalizableMessage localizableMessage = builder
                    .fallbackMessage("Failed to execute reset password. Bad request.")
                    .key("execute.reset.credential.bad.request").build();
            response = response.message(LocalizationUtil.createLocalizableMessageType(localizableMessage));
            throw new SchemaException(localizableMessage);

        }

        SecurityPolicyType securityPolicy = getSecurityPolicy(user, task, parentResult);
        CredentialsResetPolicyType resetPolicyType = securityPolicy.getCredentialsReset();
        //TODO: search according tot he credentialID and others
        if (resetPolicyType == null) {
            LocalizableMessage localizableMessage = builder
                    .fallbackMessage("Failed to execute reset password. Bad configuration.")
                    .key("execute.reset.credential.bad.configuration").build();
            response = response.message(LocalizationUtil.createLocalizableMessageType(localizableMessage));
            throw new SchemaException(localizableMessage);
        }

        if (!resetMethod.equals(resetPolicyType.getName())) {
            LocalizableMessage localizableMessage = builder
                    .fallbackMessage("Failed to execute reset password. Bad method.")
                    .key("execute.reset.credential.bad.method").build();
            response = response.message(LocalizationUtil.createLocalizableMessageType(localizableMessage));
            throw new SchemaException(localizableMessage);
        }

        CredentialSourceType credentialSourceType = resetPolicyType.getNewCredentialSource();

        if (credentialSourceType == null) {
            //TODO: go through deprecated functionality
            LocalizableMessage localizableMessage = builder
                    .fallbackMessage("Failed to execute reset password. No credential source.")
                    .key("execute.reset.credential.no.credential.source").build();
            response = response.message(LocalizationUtil.createLocalizableMessageType(localizableMessage));
            //for now just let the user know that he needs to specify it
            return response;
        }

        ValuePolicyType valuePolicy = getValuePolicy(user, task, parentResult);

        ObjectDelta<UserType> userDelta = null;
        if (credentialSourceType.getUserEntry() != null) {
            PolicyItemDefinitionType policyItemDefinitione = new PolicyItemDefinitionType();
            policyItemDefinitione.setValue(executeCredentialResetRequest.getUserEntry());

            if (!validateValue(user, valuePolicy, policyItemDefinitione, task, parentResult)) {
                LOGGER.error("Cannot execute reset password. New password doesn't satisfy policy constraints");
                parentResult.recordFatalError(
                        "Cannot execute reset password. New password doesn't satisfy policy constraints");
                LocalizableMessage localizableMessage = builder
                        .fallbackMessage("New password doesn't satisfy policy constraints.")
                        .key("execute.reset.credential.validation.failed").build();
                throw new PolicyViolationException(localizableMessage);
            }

            ProtectedStringType newProtectedPassword = new ProtectedStringType();
            newProtectedPassword.setClearValue(executeCredentialResetRequest.getUserEntry());
            userDelta = ObjectDelta.createModificationReplaceProperty(UserType.class, user.getOid(),
                    SchemaConstants.PATH_PASSWORD_VALUE, prismContext, newProtectedPassword);

        }

        if (BooleanUtils.isTrue(resetPolicyType.isForceChange())) {
            if (userDelta != null) {
                userDelta.addModificationReplaceProperty(SchemaConstants.PATH_PASSWORD_FORCE_CHANGE, Boolean.TRUE);
            }
        }

        try {
            Collection<ObjectDeltaOperation<? extends ObjectType>> result = modelService.executeChanges(
                    MiscUtil.createCollection(userDelta), ModelExecuteOptions.createRaw(), task, parentResult);
        } catch (ObjectNotFoundException | SchemaException | CommunicationException | ConfigurationException
                | SecurityViolationException | ExpressionEvaluationException | ObjectAlreadyExistsException
                | PolicyViolationException e) {
            response.message(
                    LocalizationUtil.createForFallbackMessage("Failed to reset credential: " + e.getMessage()));
            throw e;
        }

        parentResult.recomputeStatus();
        LocalizableMessage message = builder.fallbackMessage("Reset password was successful")
                .key("execute.reset.credential.successful").fallbackLocalizableMessage(null).build();
        response.setMessage(LocalizationUtil.createLocalizableMessageType(message));

        return response;
    }

    public void clearCaches() {
        cacheRegistry.clearAllCaches();
    }

}