com.evolveum.midpoint.security.enforcer.impl.SecurityEnforcerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.security.enforcer.impl.SecurityEnforcerImpl.java

Source

/*
 * Copyright (c) 2014-2018 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.security.enforcer.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;

import javax.xml.namespace.QName;

import org.apache.commons.lang.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContainer;
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.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PlusMinusZero;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.AllFilter;
import com.evolveum.midpoint.prism.query.AndFilter;
import com.evolveum.midpoint.prism.query.InOidFilter;
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.QueryJaxbConvertor;
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.prism.query.builder.S_AtomicFilterExit;
import com.evolveum.midpoint.prism.query.builder.S_FilterEntryOrEmpty;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.repo.api.query.ObjectFilterExpressionEvaluator;
import com.evolveum.midpoint.repo.common.expression.ExpressionFactory;
import com.evolveum.midpoint.repo.common.expression.ExpressionUtil;
import com.evolveum.midpoint.repo.common.expression.ExpressionVariables;
import com.evolveum.midpoint.schema.constants.ExpressionConstants;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.security.api.Authorization;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.OwnerResolver;
import com.evolveum.midpoint.security.api.SecurityContextManager;
import com.evolveum.midpoint.security.enforcer.api.AccessDecision;
import com.evolveum.midpoint.security.enforcer.api.AuthorizationParameters;
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.task.api.TaskManager;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.AuthorizationException;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractRoleType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationDecisionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationLimitationsType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgRelationObjectSpecificationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgScopeType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OwnedObjectSelectorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleRelationObjectSpecificationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SpecialObjectSpecificationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.SubjectedObjectSelectorType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;

/**
 * @author Radovan Semancik
 *
 */
@Component("securityEnforcer")
public class SecurityEnforcerImpl implements SecurityEnforcer {

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

    private static final boolean FILTER_TRACE_ENABLED = false;

    @Autowired
    @Qualifier("cacheRepositoryService")
    private RepositoryService repositoryService;

    @Autowired
    private TaskManager taskManager;
    @Autowired
    private ExpressionFactory expressionFactory;
    @Autowired
    private PrismContext prismContext;

    @Autowired
    @Qualifier("securityContextManager")
    private SecurityContextManager securityContextManager;

    @Override
    public <O extends ObjectType, T extends ObjectType> boolean isAuthorized(String operationUrl,
            AuthorizationPhaseType phase, AuthorizationParameters<O, T> params, OwnerResolver ownerResolver,
            Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        AccessDecision decision = isAuthorizedInternal(getMidPointPrincipal(), operationUrl, phase, params,
                ownerResolver, null, task, result);
        return decision.equals(AccessDecision.ALLOW);
    }

    private <O extends ObjectType, T extends ObjectType> AccessDecision isAuthorizedInternal(
            MidPointPrincipal midPointPrincipal, String operationUrl, AuthorizationPhaseType phase,
            AuthorizationParameters<O, T> params, OwnerResolver ownerResolver,
            Consumer<Authorization> applicableAutzConsumer, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        if (phase == null) {
            AccessDecision requestPhaseDecision = isAuthorizedPhase(midPointPrincipal, operationUrl,
                    AuthorizationPhaseType.REQUEST, params, ownerResolver, applicableAutzConsumer, task, result);
            if (!requestPhaseDecision.equals(AccessDecision.ALLOW)) {
                return requestPhaseDecision;
            }
            return isAuthorizedPhase(midPointPrincipal, operationUrl, AuthorizationPhaseType.EXECUTION, params,
                    ownerResolver, applicableAutzConsumer, task, result);
        } else {
            return isAuthorizedPhase(midPointPrincipal, operationUrl, phase, params, ownerResolver,
                    applicableAutzConsumer, task, result);
        }
    }

    private <O extends ObjectType, T extends ObjectType> AccessDecision isAuthorizedPhase(
            MidPointPrincipal midPointPrincipal, String operationUrl, AuthorizationPhaseType phase,
            AuthorizationParameters<O, T> params, OwnerResolver ownerResolver,
            Consumer<Authorization> applicableAutzConsumer, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {

        if (AuthorizationConstants.AUTZ_NO_ACCESS_URL.equals(operationUrl)) {
            return AccessDecision.DENY;
        }

        if (phase == null) {
            throw new IllegalArgumentException("No phase");
        }
        AccessDecision decision = AccessDecision.DEFAULT;
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("AUTZ: evaluating authorization principal={}, op={}, phase={}, {}",
                    getUsername(midPointPrincipal), operationUrl, phase, params.shortDump());
        }
        final AutzItemPaths allowedItems = new AutzItemPaths();
        Collection<Authorization> authorities = getAuthorities(midPointPrincipal);
        if (authorities != null) {
            for (GrantedAuthority authority : authorities) {
                if (authority instanceof Authorization) {
                    Authorization autz = (Authorization) authority;
                    String autzHumanReadableDesc = autz.getHumanReadableDesc();
                    LOGGER.trace("  Evaluating {}", autzHumanReadableDesc);

                    // First check if the authorization is applicable.

                    // action
                    if (!autz.getAction().contains(operationUrl)
                            && !autz.getAction().contains(AuthorizationConstants.AUTZ_ALL_URL)) {
                        LOGGER.trace("    {} not applicable for operation {}", autzHumanReadableDesc, operationUrl);
                        continue;
                    }

                    // phase
                    if (autz.getPhase() == null) {
                        LOGGER.trace("    {} is applicable for all phases (continuing evaluation)",
                                autzHumanReadableDesc);
                    } else {
                        if (autz.getPhase() != phase) {
                            LOGGER.trace("    {} is not applicable for phases {} (breaking evaluation)",
                                    autzHumanReadableDesc, phase);
                            continue;
                        } else {
                            LOGGER.trace("    {} is applicable for phases {} (continuing evaluation)",
                                    autzHumanReadableDesc, phase);
                        }
                    }

                    // relation
                    if (!isApplicableRelation(autz, params.getRelation())) {
                        LOGGER.trace("    {} not applicable for relation {}", autzHumanReadableDesc,
                                params.getRelation());
                        continue;
                    }

                    // object
                    if (isApplicable(autz.getObject(), params.getObject(), midPointPrincipal, ownerResolver,
                            "object", autzHumanReadableDesc, task, result)) {
                        LOGGER.trace("    {} applicable for object {} (continuing evaluation)",
                                autzHumanReadableDesc, params.getObject());
                    } else {
                        LOGGER.trace(
                                "    {} not applicable for object {}, none of the object specifications match (breaking evaluation)",
                                autzHumanReadableDesc, params.getObject());
                        continue;
                    }

                    // target
                    if (isApplicable(autz.getTarget(), params.getTarget(), midPointPrincipal, ownerResolver,
                            "target", autzHumanReadableDesc, task, result)) {
                        LOGGER.trace("    {} applicable for target {} (continuing evaluation)",
                                autzHumanReadableDesc, params.getObject());
                    } else {
                        LOGGER.trace(
                                "    {} not applicable for target {}, none of the target specifications match (breaking evaluation)",
                                autzHumanReadableDesc, params.getObject());
                        continue;
                    }

                    if (applicableAutzConsumer != null) {
                        applicableAutzConsumer.accept(autz);
                    }

                    // authority is applicable to this situation. now we can process the decision.
                    AuthorizationDecisionType autzDecision = autz.getDecision();
                    if (autzDecision == null || autzDecision.equals(AuthorizationDecisionType.ALLOW)) {
                        allowedItems.collectItems(autz);
                        LOGGER.trace("    {}: ALLOW operation {} (but continue evaluation)", autzHumanReadableDesc,
                                operationUrl);
                        decision = AccessDecision.ALLOW;
                        // Do NOT break here. Other authorization statements may still deny the operation
                    } else {
                        // item
                        if (isApplicableItem(autz, params.getObject(), params.getDelta())) {
                            LOGGER.trace("    {}: Deny authorization applicable for items (continuing evaluation)",
                                    autzHumanReadableDesc);
                        } else {
                            LOGGER.trace("    {} not applicable for items (breaking evaluation)",
                                    autzHumanReadableDesc);
                            continue;
                        }
                        LOGGER.trace("    {}: DENY operation {}", autzHumanReadableDesc, operationUrl);
                        decision = AccessDecision.DENY;
                        // Break right here. Deny cannot be overridden by allow. This decision cannot be changed.
                        break;
                    }

                } else {
                    LOGGER.warn("Unknown authority type {} in user {}", authority.getClass(),
                            getUsername(midPointPrincipal));
                }
            }
        }

        if (decision.equals(AccessDecision.ALLOW)) {
            // Still check allowedItems. We may still deny the operation.
            if (allowedItems.isAllItems()) {
                // This means all items are allowed. No need to check anything
                LOGGER.trace("  Empty list of allowed items, operation allowed");
            } else {
                // all items in the object and delta must be allowed
                LOGGER.trace("  Checking for allowed items: {}", allowedItems);

                ItemDecisionFunction itemDecisionFunction = (itemPath,
                        removingContainer) -> decideAllowedItems(itemPath, allowedItems, phase, removingContainer);
                AccessDecision itemsDecision = null;
                if (params.hasDelta()) {
                    itemsDecision = determineDeltaDecision(params.getDelta(), params.getObject(),
                            itemDecisionFunction);
                } else if (params.hasObject()) {
                    itemsDecision = determineObjectDecision(params.getObject(), itemDecisionFunction);
                }
                if (itemsDecision != AccessDecision.ALLOW) {
                    LOGGER.trace("    NOT ALLOWED operation because the item decision is {}", itemsDecision);
                    decision = AccessDecision.DEFAULT;
                }
            }
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("AUTZ result: principal={}, operation={}: {}", getUsername(midPointPrincipal),
                    prettyActionUrl(operationUrl), decision);
        }
        return decision;
    }

    private AccessDecision decideAllowedItems(final ItemPath itemPath, final AutzItemPaths allowedItems,
            final AuthorizationPhaseType phase, final boolean removingContainer) {
        if (isAllowedItem(itemPath, allowedItems, phase, removingContainer)) {
            return AccessDecision.ALLOW;
        } else {
            return AccessDecision.DEFAULT;
        }
    }

    private <O extends ObjectType> AccessDecision determineObjectDecision(PrismContainer<O> object,
            ItemDecisionFunction itemDecitionFunction) {
        AccessDecision containerDecision = determineContainerDecision(object.getValue(), itemDecitionFunction,
                false, "object");
        if (containerDecision == null && object.isEmpty()) {
            // There are no items in the object. Therefore there is no item that is allowed. Therefore decision is DEFAULT.
            // But also there is no item that is denied or not allowed. 
            // This is a corner case. But this approach is often used by GUI to determine if
            // a specific class of object is allowed, e.g. if it is allowed to create (some) roles. This is used to
            // determine whether to display a particular menu item.
            // Therefore we should allow such cases.
            return AccessDecision.ALLOW;
        }
        return containerDecision;
    }

    private <C extends Containerable, O extends ObjectType> AccessDecision determineContainerDeltaDecision(
            ContainerDelta<C> cdelta, PrismObject<O> currentObject, ItemDecisionFunction itemDecitionFunction) {
        AccessDecision decision = null;
        ItemPath path = cdelta.getPath();

        // Everything is plain and simple for add. No need for any additional checks.
        Collection<PrismContainerValue<C>> valuesToAdd = cdelta.getValuesToAdd();
        if (valuesToAdd != null) {
            for (PrismContainerValue<C> cval : valuesToAdd) {
                AccessDecision subdecision = determineContainerDecision(cval, itemDecitionFunction, false,
                        "delta add");
                decision = AccessDecision.combine(decision, subdecision);
            }
        }

        // For deleted container values watch out for id-only deltas. Those deltas do not have
        // any subitems in them. So we need to use data from currentObject for autz evaluation.
        Collection<PrismContainerValue<C>> valuesToDelete = cdelta.getValuesToDelete();
        if (valuesToDelete != null) {
            for (PrismContainerValue<C> cval : valuesToDelete) {
                AccessDecision subdecision = null;
                if (cval.isIdOnly()) {
                    PrismContainerValue<C> currentObjectCval = determineContainerValueFromCurrentObject(path,
                            cval.getId(), currentObject);
                    if (currentObjectCval != null) {
                        subdecision = determineContainerDecision(currentObjectCval, itemDecitionFunction, true,
                                "delta delete (current value)");
                    }
                } else {
                    subdecision = determineContainerDecision(cval, itemDecitionFunction, true, "delta delete");
                }
                if (subdecision != null) {
                    decision = AccessDecision.combine(decision, subdecision);
                }
            }
        }

        // Values to replace should pass the ordinary check. But we also need to check old values
        // in currentObject, because those values are efficiently deleted.
        Collection<PrismContainerValue<C>> valuesToReplace = cdelta.getValuesToReplace();
        if (valuesToReplace != null) {
            for (PrismContainerValue<C> cval : valuesToReplace) {
                AccessDecision subdecision = determineContainerDecision(cval, itemDecitionFunction, false,
                        "delta replace");
                decision = AccessDecision.combine(decision, subdecision);
            }
            Collection<PrismContainerValue<C>> oldCvals = determineContainerValuesFromCurrentObject(path,
                    currentObject);
            if (oldCvals != null) {
                for (PrismContainerValue<C> cval : oldCvals) {
                    AccessDecision subdecision = determineContainerDecision(cval, itemDecitionFunction, true,
                            "delta replace (removed current value)");
                    decision = AccessDecision.combine(decision, subdecision);
                }
            }
        }

        return decision;
    }

    private <C extends Containerable> void logSubitemContainerDecision(AccessDecision subdecision, String location,
            PrismContainerValue<C> cval) {
        if (LOGGER.isTraceEnabled()) {
            if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuhotizationLog()) {
                LOGGER.trace("    container {} for {} (processed subitems): decision={}", cval.getPath(), location,
                        subdecision);
            }
        }
    }

    private <C extends Containerable> void logSubitemDecision(AccessDecision subdecision, String location,
            ItemPath path) {
        if (LOGGER.isTraceEnabled()) {
            if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuhotizationLog()) {
                LOGGER.trace("    item {} for {}: decision={}", path, location, subdecision);
            }
        }
    }

    private <C extends Containerable, O extends ObjectType> PrismContainerValue<C> determineContainerValueFromCurrentObject(
            ItemPath path, long id, PrismObject<O> currentObject) {
        Collection<PrismContainerValue<C>> oldCvals = determineContainerValuesFromCurrentObject(path,
                currentObject);
        if (oldCvals == null) {
            return null;
        }
        for (PrismContainerValue<C> oldCval : oldCvals) {
            if (id == oldCval.getId()) {
                return oldCval;
            }
        }
        return null;
    }

    private <C extends Containerable, O extends ObjectType> Collection<PrismContainerValue<C>> determineContainerValuesFromCurrentObject(
            ItemPath path, PrismObject<O> currentObject) {
        PrismContainer<C> container = currentObject.findContainer(path);
        if (container == null) {
            return null;
        }
        return container.getValues();
    }

    private AccessDecision determineContainerDecision(PrismContainerValue<?> cval,
            ItemDecisionFunction itemDecitionFunction, boolean removingContainer, String decisionContextDesc) {
        List<Item<?, ?>> items = cval.getItems();
        // Note: cval.isEmpty() will also check for id. We do not care about that.
        if (items == null || items.isEmpty()) {
            // TODO: problem with empty containers such as
            // orderConstraint in assignment. Skip all
            // empty items ... for now.
            logSubitemContainerDecision(null, decisionContextDesc, cval);
            return null;
        }
        AccessDecision decision = null;
        for (Item<?, ?> item : items) {
            ItemPath itemPath = item.getPath();
            AccessDecision itemDecision = itemDecitionFunction.decide(itemPath.namedSegmentsOnly(),
                    removingContainer);
            logSubitemDecision(itemDecision, decisionContextDesc, itemPath);
            if (itemDecision == null) {
                // null decision means: skip this
                continue;
            }
            if (itemDecision == AccessDecision.DEFAULT && item instanceof PrismContainer<?>) {
                // No decision for entire container. Subitems will dictate the decision.
                List<PrismContainerValue<?>> subValues = (List) ((PrismContainer<?>) item).getValues();
                AccessDecision containerDecision = null;
                for (PrismContainerValue<?> subValue : subValues) {
                    AccessDecision subdecision = determineContainerDecision(subValue, itemDecitionFunction,
                            removingContainer, decisionContextDesc);
                    containerDecision = AccessDecision.combine(containerDecision, subdecision);
                    // We do not want to break the loop immediately here. We want all the denied items to get logged
                }
                if (containerDecision == null) {
                    // All items that were inside all the container values are ignored (e.g. metadata).
                    // This is efficiently the same situation as an empty container.
                    // So just ignore it.
                    continue;
                } else {
                    decision = AccessDecision.combine(decision, containerDecision);
                }
            } else {
                if (itemDecision == AccessDecision.DENY) {
                    LOGGER.trace("  DENY operation because item {} in the object is not allowed", itemPath);
                    // We do not want to break the loop immediately here. We want all the denied items to get logged
                }
                decision = AccessDecision.combine(decision, itemDecision);
            }
        }
        logSubitemContainerDecision(decision, decisionContextDesc, cval);
        return decision;
    }

    /**
     * The currentObject parameter is the state of the object as we have seen it (the more recent the better).
     * This is used to check authorization for id-only delete deltas and replace deltas for containers.
     */
    private <O extends ObjectType> AccessDecision determineDeltaDecision(ObjectDelta<O> delta,
            PrismObject<O> currentObject, ItemDecisionFunction itemDecisionFunction) {
        if (delta.isAdd()) {
            return determineObjectDecision(delta.getObjectToAdd(), itemDecisionFunction);
        } else {
            AccessDecision decision = null;
            for (ItemDelta<?, ?> itemDelta : delta.getModifications()) {
                ItemPath itemPath = itemDelta.getPath();
                AccessDecision itemDecision = itemDecisionFunction.decide(itemPath.namedSegmentsOnly(), false);
                if (itemDecision == null) {
                    // null decision means: skip this
                    continue;
                }
                if (itemDecision == AccessDecision.DEFAULT && itemDelta instanceof ContainerDelta<?>) {
                    // No decision for entire container. Subitems will dictate the decision.
                    AccessDecision subdecision = determineContainerDeltaDecision((ContainerDelta<?>) itemDelta,
                            currentObject, itemDecisionFunction);
                    decision = AccessDecision.combine(decision, subdecision);
                } else {
                    if (itemDecision == AccessDecision.DENY) {
                        LOGGER.trace("  DENY operation because item {} in the delta is not allowed", itemPath);
                        // We do not want to break the loop immediately here. We want all the denied items to get logged
                    }
                    decision = AccessDecision.combine(decision, itemDecision);
                }
            }
            return decision;
        }
    }

    private boolean isAllowedItem(ItemPath nameOnlyItemPath, AutzItemPaths allowedItems,
            AuthorizationPhaseType phase, boolean removingContainer) {
        if (removingContainer && isInList(nameOnlyItemPath,
                AuthorizationConstants.OPERATIONAL_ITEMS_ALLOWED_FOR_CONTAINER_DELETE)) {
            return true;
        }
        if (AuthorizationPhaseType.EXECUTION.equals(phase)
                && isInList(nameOnlyItemPath, AuthorizationConstants.EXECUTION_ITEMS_ALLOWED_BY_DEFAULT)) {
            return true;
        }
        return allowedItems.isApplicable(nameOnlyItemPath);
    }

    private boolean isInList(ItemPath itemPath, Collection<ItemPath> allowedItems) {
        boolean itemAllowed = false;
        for (ItemPath allowedPath : allowedItems) {
            if (allowedPath.isSubPathOrEquivalent(itemPath)) {
                itemAllowed = true;
                break;
            }
        }
        return itemAllowed;
    }

    @Override
    public <O extends ObjectType, T extends ObjectType> void authorize(String operationUrl,
            AuthorizationPhaseType phase, AuthorizationParameters<O, T> params, OwnerResolver ownerResolver,
            Task task, OperationResult result) throws SecurityViolationException, SchemaException,
            ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException {
        boolean allow = isAuthorized(operationUrl, phase, params, ownerResolver, task, result);
        if (!allow) {
            failAuthorization(operationUrl, phase, params, result);
        }
    }

    @Override
    public <O extends ObjectType, T extends ObjectType> void failAuthorization(String operationUrl,
            AuthorizationPhaseType phase, AuthorizationParameters<O, T> params, OperationResult result)
            throws SecurityViolationException {
        MidPointPrincipal principal = securityContextManager.getPrincipal();
        String username = getQuotedUsername(principal);
        String message;
        if (params.getTarget() == null && params.getObject() == null) {
            message = "User '" + username + "' not authorized for operation " + operationUrl;
        } else if (params.getTarget() == null) {
            message = "User '" + username + "' not authorized for operation " + operationUrl + " on "
                    + params.getObject();
        } else {
            message = "User '" + username + "' not authorized for operation " + operationUrl + " on "
                    + params.getObject() + " with target " + params.getTarget();
        }
        LOGGER.error("{}", message);
        AuthorizationException e = new AuthorizationException(message);
        result.recordFatalError(e.getMessage(), e);
        throw e;
    }

    private <O extends ObjectType> boolean isApplicable(List<OwnedObjectSelectorType> objectSpecTypes,
            PrismObject<O> object, MidPointPrincipal midPointPrincipal, OwnerResolver ownerResolver, String desc,
            String autzHumanReadableDesc, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        if (objectSpecTypes != null && !objectSpecTypes.isEmpty()) {
            if (object == null) {
                LOGGER.trace("  {} not applicable for null {}", autzHumanReadableDesc, desc);
                return false;
            }
            for (OwnedObjectSelectorType autzObject : objectSpecTypes) {
                if (isApplicable(autzObject, object, midPointPrincipal, ownerResolver, desc, autzHumanReadableDesc,
                        task, result)) {
                    return true;
                }
            }
            return false;
        } else {
            LOGGER.trace("    {}: No {} specification in authorization (authorization is applicable)",
                    autzHumanReadableDesc, desc);
            return true;
        }
    }

    private <O extends ObjectType> boolean isApplicable(SubjectedObjectSelectorType objectSelector,
            PrismObject<O> object, MidPointPrincipal principal, OwnerResolver ownerResolver, String desc,
            String autzHumanReadableDesc, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        ObjectFilterExpressionEvaluator filterExpressionEvaluator = createFilterEvaluator(principal, desc,
                autzHumanReadableDesc, task, result);
        if (!repositoryService.selectorMatches(objectSelector, object, filterExpressionEvaluator, LOGGER,
                "    " + autzHumanReadableDesc + " not applicable for " + desc + " because of")) {
            return false;
        }

        OrgRelationObjectSpecificationType specOrgRelation = objectSelector.getOrgRelation();
        RoleRelationObjectSpecificationType specRoleRelation = objectSelector.getRoleRelation();

        // Special
        List<SpecialObjectSpecificationType> specSpecial = objectSelector.getSpecial();
        if (specSpecial != null && !specSpecial.isEmpty()) {
            if (objectSelector.getFilter() != null || objectSelector.getOrgRef() != null || specOrgRelation != null
                    || specRoleRelation != null) {
                throw new SchemaException("Both filter/org/role and special " + desc
                        + " specification specified in " + autzHumanReadableDesc);
            }
            for (SpecialObjectSpecificationType special : specSpecial) {
                if (special == SpecialObjectSpecificationType.SELF) {
                    String principalOid = principal != null ? principal.getOid() : null;
                    if (principalOid == null) {
                        // This is a rare case. It should not normally happen. But it may happen in tests
                        // or during initial import. Therefore we are not going to die here. Just ignore it.
                    } else {
                        if (principalOid.equals(object.getOid())) {
                            LOGGER.trace("    {}: 'self' authorization applicable for {}", autzHumanReadableDesc,
                                    desc);
                            return true;
                        } else {
                            LOGGER.trace(
                                    "    {}: 'self' authorization not applicable for {}, principal OID: {}, {} OID {}",
                                    autzHumanReadableDesc, desc, principalOid, desc, object.getOid());
                        }
                    }
                } else {
                    throw new SchemaException("Unsupported special " + desc + " specification specified in "
                            + autzHumanReadableDesc + ": " + special);
                }
            }
            return false;
        } else {
            LOGGER.trace("    {}: specials empty: {}", autzHumanReadableDesc, specSpecial);
        }

        // orgRelation
        if (specOrgRelation != null) {
            boolean match = false;
            for (ObjectReferenceType subjectParentOrgRef : principal.getUser().getParentOrgRef()) {
                if (matchesOrgRelation(object, subjectParentOrgRef, specOrgRelation, autzHumanReadableDesc, desc)) {
                    LOGGER.trace("    org {} applicable for {}, object OID {} because subject org {} matches",
                            autzHumanReadableDesc, desc, object.getOid(), subjectParentOrgRef.getOid());
                    match = true;
                    break;
                }
            }
            if (!match) {
                LOGGER.trace(
                        "    org {} not applicable for {}, object OID {} because none of the subject orgs matches",
                        autzHumanReadableDesc, desc, object.getOid());
                return false;
            }
        }

        // roleRelation
        if (specRoleRelation != null) {
            boolean match = false;
            for (ObjectReferenceType subjectRoleMembershipRef : principal.getUser().getRoleMembershipRef()) {
                if (matchesRoleRelation(object, subjectRoleMembershipRef, specRoleRelation, autzHumanReadableDesc,
                        desc)) {
                    LOGGER.trace("    {} applicable for {}, object OID {} because subject role relation {} matches",
                            autzHumanReadableDesc, desc, object.getOid(), subjectRoleMembershipRef.getOid());
                    match = true;
                    break;
                }
            }
            if (!match) {
                LOGGER.trace(
                        "    {} not applicable for {}, object OID {} because none of the subject roles matches",
                        autzHumanReadableDesc, desc, object.getOid());
                return false;
            }
        }

        if (objectSelector instanceof OwnedObjectSelectorType) {
            // Owner
            SubjectedObjectSelectorType ownerSpec = ((OwnedObjectSelectorType) objectSelector).getOwner();
            if (ownerSpec != null) {
                if (ownerResolver == null) {
                    ownerResolver = securityContextManager.getUserProfileService();
                    if (ownerResolver == null) {
                        LOGGER.trace(
                                "    {}: owner object spec not applicable for {}, object OID {} because there is no owner resolver",
                                autzHumanReadableDesc, desc, object.getOid());
                        return false;
                    }
                }
                PrismObject<? extends FocusType> owner = ownerResolver.resolveOwner(object);
                if (owner == null) {
                    LOGGER.trace(
                            "    {}: owner object spec not applicable for {}, object OID {} because it has no owner",
                            autzHumanReadableDesc, desc, object.getOid());
                    return false;
                }
                boolean ownerApplicable = isApplicable(ownerSpec, owner, principal, ownerResolver,
                        "owner of " + desc, autzHumanReadableDesc, task, result);
                if (!ownerApplicable) {
                    LOGGER.trace(
                            "    {}: owner object spec not applicable for {}, object OID {} because owner does not match (owner={})",
                            autzHumanReadableDesc, desc, object.getOid(), owner);
                    return false;
                }
            }

            // Delegator
            SubjectedObjectSelectorType delegatorSpec = ((OwnedObjectSelectorType) objectSelector).getDelegator();
            if (delegatorSpec != null) {
                if (!isSelf(delegatorSpec)) {
                    throw new SchemaException("Unsupported non-self delegator clause");
                }
                if (!object.canRepresent(UserType.class)) {
                    LOGGER.trace(
                            "    {}: delegator object spec not applicable for {}, because the object is not user",
                            autzHumanReadableDesc, desc);
                    return false;
                }
                boolean found = false;
                for (ObjectReferenceType objectDelegatedRef : ((UserType) object.asObjectable())
                        .getDelegatedRef()) {
                    if (principal.getOid().equals(objectDelegatedRef.getOid())) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    if (BooleanUtils.isTrue(delegatorSpec.isAllowInactive())) {
                        for (AssignmentType objectAssignment : ((UserType) object.asObjectable()).getAssignment()) {
                            ObjectReferenceType objectAssignmentTargetRef = objectAssignment.getTargetRef();
                            if (objectAssignmentTargetRef == null) {
                                continue;
                            }
                            if (principal.getOid().equals(objectAssignmentTargetRef.getOid())) {
                                if (QNameUtil.match(SchemaConstants.ORG_DEPUTY,
                                        objectAssignmentTargetRef.getRelation())) {
                                    found = true;
                                    break;
                                }
                            }
                        }
                    }

                    if (!found) {
                        LOGGER.trace(
                                "    {}: delegator object spec not applicable for {}, object OID {} because delegator does not match",
                                autzHumanReadableDesc, desc, object.getOid());
                        return false;
                    }
                }

            }
        }

        LOGGER.trace("    {} applicable for {} (filter)", autzHumanReadableDesc, desc);
        return true;
    }

    private ObjectFilterExpressionEvaluator createFilterEvaluator(MidPointPrincipal principal,
            String objectTargetDesc, String autzHumanReadableDesc, Task task, OperationResult result) {
        return filter -> {
            if (filter == null) {
                return null;
            }
            ExpressionVariables variables = new ExpressionVariables();
            PrismObject<UserType> subject = null;
            if (principal != null) {
                UserType userType = principal.getUser();
                if (userType != null) {
                    subject = userType.asPrismObject();
                }
            }
            variables.addVariableDefinition(ExpressionConstants.VAR_SUBJECT, subject);
            return ExpressionUtil.evaluateFilterExpressions(filter, variables, expressionFactory, prismContext,
                    "expression in " + objectTargetDesc + " in authorization " + autzHumanReadableDesc, task,
                    result);
        };
    }

    private <O extends ObjectType> ObjectFilter parseAndEvaluateFilter(MidPointPrincipal principal,
            PrismObjectDefinition<O> objectDefinition, SearchFilterType specFilterType, String objectTargetDesc,
            String autzHumanReadableDesc, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        ObjectFilter specFilter = QueryJaxbConvertor.createObjectFilter(objectDefinition, specFilterType,
                prismContext);
        if (specFilter == null) {
            return null;
        }
        ObjectFilterExpressionEvaluator filterEvaluator = createFilterEvaluator(principal, objectTargetDesc,
                autzHumanReadableDesc, task, result);
        return filterEvaluator.evaluate(specFilter);
    }

    private boolean isSelf(SubjectedObjectSelectorType spec) throws SchemaException {
        List<SpecialObjectSpecificationType> specSpecial = spec.getSpecial();
        if (specSpecial != null && !specSpecial.isEmpty()) {
            if (spec.getFilter() != null || spec.getOrgRef() != null || spec.getOrgRelation() != null
                    || spec.getRoleRelation() != null) {
                return false;
            }
            for (SpecialObjectSpecificationType special : specSpecial) {
                if (special == SpecialObjectSpecificationType.SELF) {
                    return true;
                } else {
                    throw new SchemaException(
                            "Unsupported special object specification specified in authorization: " + special);
                }
            }
        }
        return false;
    }

    private <O extends ObjectType> boolean matchesOrgRelation(PrismObject<O> object,
            ObjectReferenceType subjectParentOrgRef, OrgRelationObjectSpecificationType specOrgRelation,
            String autzHumanReadableDesc, String desc) throws SchemaException {
        if (!MiscSchemaUtil.compareRelation(specOrgRelation.getSubjectRelation(),
                subjectParentOrgRef.getRelation())) {
            return false;
        }
        if (BooleanUtils.isTrue(specOrgRelation.isIncludeReferenceOrg())
                && subjectParentOrgRef.getOid().equals(object.getOid())) {
            return true;
        }
        if (specOrgRelation.getScope() == null) {
            return repositoryService.isDescendant(object, subjectParentOrgRef.getOid());
        }
        switch (specOrgRelation.getScope()) {
        case ALL_DESCENDANTS:
            return repositoryService.isDescendant(object, subjectParentOrgRef.getOid());
        case DIRECT_DESCENDANTS:
            return hasParentOrgRef(object, subjectParentOrgRef.getOid());
        case ALL_ANCESTORS:
            return repositoryService.isAncestor(object, subjectParentOrgRef.getOid());
        default:
            throw new UnsupportedOperationException("Unknown orgRelation scope " + specOrgRelation.getScope());
        }
    }

    private <O extends ObjectType> boolean hasParentOrgRef(PrismObject<O> object, String oid) {
        List<ObjectReferenceType> objParentOrgRefs = object.asObjectable().getParentOrgRef();
        for (ObjectReferenceType objParentOrgRef : objParentOrgRefs) {
            if (oid.equals(objParentOrgRef.getOid())) {
                return true;
            }
        }
        return false;
    }

    private <O extends ObjectType> boolean matchesRoleRelation(PrismObject<O> object,
            ObjectReferenceType subjectRoleMembershipRef, RoleRelationObjectSpecificationType specRoleRelation,
            String autzHumanReadableDesc, String desc) throws SchemaException {
        if (!MiscSchemaUtil.compareRelation(specRoleRelation.getSubjectRelation(),
                subjectRoleMembershipRef.getRelation())) {
            return false;
        }
        if (BooleanUtils.isTrue(specRoleRelation.isIncludeReferenceRole())
                && subjectRoleMembershipRef.getOid().equals(object.getOid())) {
            return true;
        }
        if (!BooleanUtils.isFalse(specRoleRelation.isIncludeMembers())) {
            if (!object.canRepresent(FocusType.class)) {
                return false;
            }
            for (ObjectReferenceType objectRoleMembershipRef : ((FocusType) object.asObjectable())
                    .getRoleMembershipRef()) {
                if (!subjectRoleMembershipRef.getOid().equals(objectRoleMembershipRef.getOid())) {
                    continue;
                }
                if (!MiscSchemaUtil.compareRelation(specRoleRelation.getObjectRelation(),
                        objectRoleMembershipRef.getRelation())) {
                    continue;
                }
                return true;
            }
        }
        return false;
    }

    private <O extends ObjectType> boolean isApplicableItem(Authorization autz, PrismObject<O> object,
            ObjectDelta<O> delta) throws SchemaException {
        List<ItemPathType> itemPaths = autz.getItem();
        if (itemPaths == null || itemPaths.isEmpty()) {
            List<ItemPathType> exceptItems = autz.getExceptItem();
            if (exceptItems.isEmpty()) {
                // No item constraints. Applicable for all items.
                LOGGER.trace("  items empty");
                return true;
            } else {
                return isApplicableItem(autz, object, delta, exceptItems, false);
            }
        } else {
            return isApplicableItem(autz, object, delta, itemPaths, true);
        }

    }

    private <O extends ObjectType> boolean isApplicableItem(Authorization autz, PrismObject<O> object,
            ObjectDelta<O> delta, List<ItemPathType> itemPaths, boolean positive) throws SchemaException {
        for (ItemPathType itemPathType : itemPaths) {
            ItemPath itemPath = itemPathType.getItemPath();
            if (delta == null) {
                if (object != null) {
                    if (object.containsItem(itemPath, false)) {
                        if (positive) {
                            LOGGER.trace("  applicable object item " + itemPath);
                            return true;
                        } else {
                            LOGGER.trace("  excluded object item " + itemPath);
                            return false;
                        }
                    }
                }
            } else {
                ItemDelta<?, ?> itemDelta = delta.findItemDelta(itemPath);
                if (itemDelta != null && !itemDelta.isEmpty()) {
                    if (positive) {
                        LOGGER.trace("  applicable delta item " + itemPath);
                        return true;
                    } else {
                        LOGGER.trace("  excluded delta item " + itemPath);
                        return false;
                    }
                }
            }
        }
        if (positive) {
            LOGGER.trace("  no applicable item");
            return false;
        } else {
            LOGGER.trace("  no excluded item");
            return true;
        }
    }

    /**
     * Simple access control decision similar to that used by spring security.
     * It is practically applicable only for simple (non-parametric) cases such as access to GUI pages.
     * However, it supports authorization hierarchies. Therefore the ordering of elements in
     * required actions is important.
     */
    @Override
    public AccessDecision decideAccess(MidPointPrincipal principal, List<String> requiredActions, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException,
            CommunicationException, ConfigurationException, SecurityViolationException {

        AccessDecision finalDecision = AccessDecision.DEFAULT;
        for (String requiredAction : requiredActions) {
            AccessDecision decision = isAuthorizedInternal(principal, requiredAction, null,
                    AuthorizationParameters.EMPTY, null, null, task, result);
            if (AccessDecision.DENY.equals(decision)) {
                return AccessDecision.DENY;
            }
            if (AccessDecision.ALLOW.equals(decision)) {
                finalDecision = AccessDecision.ALLOW;
            }
        }
        return finalDecision;
    }

    private String getQuotedUsername(Authentication authentication) {
        String username = "(none)";
        Object principal = authentication.getPrincipal();
        if (principal != null) {
            if (principal instanceof MidPointPrincipal) {
                username = "'" + ((MidPointPrincipal) principal).getUsername() + "'";
            } else {
                username = "(unknown:" + principal + ")";
            }
        }
        return username;
    }

    private String getQuotedUsername(MidPointPrincipal principal) {
        if (principal == null) {
            return "(none)";
        }
        return "'" + principal.getUsername() + "'";
    }

    private MidPointPrincipal getMidPointPrincipal() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            LOGGER.warn("No authentication");
            return null;
        }
        Object principal = authentication.getPrincipal();
        if (principal == null) {
            LOGGER.warn("Null principal");
            return null;
        }
        if (!(principal instanceof MidPointPrincipal)) {
            if (authentication.getPrincipal() instanceof String && "anonymousUser".equals(principal)) {
                return null;
            }
            LOGGER.warn("Unknown principal type {}", principal.getClass());
            return null;
        }
        return (MidPointPrincipal) principal;
    }

    private Collection<Authorization> getAuthorities(MidPointPrincipal principal) {
        if (principal == null) {
            // Anonymous access, possibly with elevated privileges
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            Collection<Authorization> authorizations = new ArrayList<>();
            if (authentication != null) {
                for (GrantedAuthority authority : authentication.getAuthorities()) {
                    if (authority instanceof Authorization) {
                        authorizations.add((Authorization) authority);
                    }
                }
            }
            return authorizations;
        } else {
            return principal.getAuthorities();
        }
    }

    @Override
    public <O extends ObjectType> ObjectSecurityConstraints compileSecurityConstraints(PrismObject<O> object,
            OwnerResolver ownerResolver, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        MidPointPrincipal principal = getMidPointPrincipal();
        if (object == null) {
            throw new IllegalArgumentException("Cannot compile security constraints of null object");
        }
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("AUTZ: evaluating security constraints principal={}, object={}", getUsername(principal),
                    object);
        }
        ObjectSecurityConstraintsImpl objectSecurityConstraints = new ObjectSecurityConstraintsImpl();
        Collection<Authorization> authorities = getAuthorities(principal);
        if (authorities != null) {
            for (Authorization autz : authorities) {
                String autzHumanReadableDesc = autz.getHumanReadableDesc();
                LOGGER.trace("  Evaluating {}", autzHumanReadableDesc);

                // skip action applicability evaluation. We are interested in all actions

                // object
                if (isApplicable(autz.getObject(), object, principal, ownerResolver, "object",
                        autzHumanReadableDesc, task, result)) {
                    LOGGER.trace("    {} applicable for object {} (continuing evaluation)", autzHumanReadableDesc,
                            object);
                } else {
                    LOGGER.trace(
                            "    {} not applicable for object {}, none of the object specifications match (breaking evaluation)",
                            autzHumanReadableDesc, object);
                    continue;
                }

                // skip target applicability evaluation. We do not have a target here

                objectSecurityConstraints.applyAuthorization(autz);
            }
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("AUTZ: evaluated security constraints principal={}, object={}:\n{}",
                    getUsername(principal), object, objectSecurityConstraints.debugDump(1));
        }

        return objectSecurityConstraints;
    }

    @Override
    public <T extends ObjectType, O extends ObjectType> ObjectFilter preProcessObjectFilter(String operationUrl,
            AuthorizationPhaseType phase, Class<T> searchResultType, PrismObject<O> object, ObjectFilter origFilter,
            String limitAuthorizationAction, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        MidPointPrincipal principal = getMidPointPrincipal();
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(
                    "AUTZ: evaluating search pre-process principal={}, searchResultType={}, object={}: orig filter {}",
                    getUsername(principal), searchResultType, object, origFilter);
        }
        if (origFilter == null) {
            origFilter = AllFilter.createAll();
        }
        ObjectFilter finalFilter;
        if (phase != null) {
            finalFilter = preProcessObjectFilterInternal(principal, operationUrl, phase, true, searchResultType,
                    object, true, origFilter, limitAuthorizationAction, "search pre-process", task, result);
        } else {
            ObjectFilter filterBoth = preProcessObjectFilterInternal(principal, operationUrl, null, false,
                    searchResultType, object, true, origFilter, limitAuthorizationAction, "search pre-process",
                    task, result);
            ObjectFilter filterRequest = preProcessObjectFilterInternal(principal, operationUrl,
                    AuthorizationPhaseType.REQUEST, false, searchResultType, object, true, origFilter,
                    limitAuthorizationAction, "search pre-process", task, result);
            ObjectFilter filterExecution = preProcessObjectFilterInternal(principal, operationUrl,
                    AuthorizationPhaseType.EXECUTION, false, searchResultType, object, true, origFilter,
                    limitAuthorizationAction, "search pre-process", task, result);
            finalFilter = ObjectQueryUtil.filterOr(filterBoth,
                    ObjectQueryUtil.filterAnd(filterRequest, filterExecution));
        }
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("AUTZ: evaluated search pre-process principal={}, objectType={}: {}",
                    getUsername(principal), getObjectType(searchResultType), finalFilter);
        }
        if (finalFilter instanceof AllFilter) {
            // compatibility
            return null;
        }
        return finalFilter;
    }

    @Override
    public <T extends ObjectType, O extends ObjectType> boolean canSearch(String operationUrl,
            AuthorizationPhaseType phase, Class<T> searchResultType, PrismObject<O> object, boolean includeSpecial,
            ObjectFilter filter, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        MidPointPrincipal principal = getMidPointPrincipal();
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(
                    "AUTZ: evaluating search permission principal={}, searchResultType={}, object={}: filter {}",
                    getUsername(principal), searchResultType, object, filter);
        }
        if (filter == null) {
            return true;
        }
        ObjectFilter finalFilter;
        if (phase != null) {
            finalFilter = preProcessObjectFilterInternal(principal, operationUrl, phase, true, searchResultType,
                    object, includeSpecial, filter, null, "search permission", task, result);
        } else {
            ObjectFilter filterBoth = preProcessObjectFilterInternal(principal, operationUrl, null, false,
                    searchResultType, object, includeSpecial, filter, null, "search permission", task, result);
            ObjectFilter filterRequest = preProcessObjectFilterInternal(principal, operationUrl,
                    AuthorizationPhaseType.REQUEST, false, searchResultType, object, includeSpecial, filter, null,
                    "search permission", task, result);
            ObjectFilter filterExecution = preProcessObjectFilterInternal(principal, operationUrl,
                    AuthorizationPhaseType.EXECUTION, false, searchResultType, object, includeSpecial, filter, null,
                    "search permission", task, result);
            finalFilter = ObjectQueryUtil.filterOr(filterBoth,
                    ObjectQueryUtil.filterAnd(filterRequest, filterExecution));
        }
        finalFilter = ObjectQueryUtil.simplify(finalFilter);
        boolean decision = !(finalFilter instanceof NoneFilter);
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(
                    "AUTZ: evaluated search permission principal={}, objectType={}, evulated from filter: {}: decision={}",
                    getUsername(principal), getObjectType(searchResultType), finalFilter, decision);
        }
        return decision;
    }

    private <T extends ObjectType, O extends ObjectType> ObjectFilter preProcessObjectFilterInternal(
            MidPointPrincipal principal, String operationUrl, AuthorizationPhaseType phase,
            boolean includeNullPhase, Class<T> objectType, PrismObject<O> object, boolean includeSpecial,
            ObjectFilter origFilter, String limitAuthorizationAction, String desc, Task task,
            OperationResult result) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException,
            CommunicationException, ConfigurationException, SecurityViolationException {

        Collection<Authorization> authorities = getAuthorities(principal);

        ObjectFilter securityFilterAllow = null;
        ObjectFilter securityFilterDeny = null;

        QueryAutzItemPaths queryItemsSpec = new QueryAutzItemPaths();
        queryItemsSpec.addRequiredItems(origFilter); // MID-3916
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("  phase={}, initial query items spec {}", phase, queryItemsSpec.shortDump());
        }

        boolean hasAllowAll = false;
        if (authorities != null) {
            for (GrantedAuthority authority : authorities) {
                if (authority instanceof Authorization) {
                    Authorization autz = (Authorization) authority;
                    String autzHumanReadableDesc = autz.getHumanReadableDesc();
                    LOGGER.trace("  Evaluating {}", autzHumanReadableDesc);

                    // action
                    if (!autz.getAction().contains(operationUrl)
                            && !autz.getAction().contains(AuthorizationConstants.AUTZ_ALL_URL)) {
                        LOGGER.trace("    Authorization not applicable for operation {}", operationUrl);
                        continue;
                    }

                    // phase
                    if (autz.getPhase() == phase || (includeNullPhase && autz.getPhase() == null)) {
                        LOGGER.trace("    Authorization is applicable for phases {} (continuing evaluation)",
                                phase);
                    } else {
                        LOGGER.trace("    Authorization is not applicable for phase {}", phase);
                        continue;
                    }

                    if (!isApplicableLimitations(autz, limitAuthorizationAction)) {
                        LOGGER.trace(
                                "    Authorization is limited to other action, not applicable for operation {}",
                                operationUrl);
                        continue;
                    }

                    // object or target
                    String objectTargetSpec;
                    ObjectFilter autzObjSecurityFilter = null;
                    List<OwnedObjectSelectorType> objectSpecTypes;
                    if (object == null) {
                        // object not present. Therefore we are looking for object here
                        objectSpecTypes = autz.getObject();
                        objectTargetSpec = "object";
                    } else {
                        // object present. Therefore we are looking for target
                        objectSpecTypes = autz.getTarget();
                        objectTargetSpec = "target";

                        // .. but we need to decide whether this authorization is applicable to the object
                        if (isApplicable(autz.getObject(), object, principal, null, "object", autzHumanReadableDesc,
                                task, result)) {
                            LOGGER.trace("    Authorization is applicable for object {}", object);
                        } else {
                            LOGGER.trace("    Authorization is not applicable for object {}", object);
                            continue;
                        }
                    }

                    boolean applicable = true;
                    if (objectSpecTypes == null || objectSpecTypes.isEmpty()) {

                        LOGGER.trace(
                                "    No {} specification in authorization (authorization is universaly applicable)",
                                objectTargetSpec);
                        autzObjSecurityFilter = AllFilter.createAll();

                    } else {

                        applicable = false;
                        for (OwnedObjectSelectorType objectSpecType : objectSpecTypes) {
                            ObjectFilter objSpecSecurityFilter = null;
                            TypeFilter objSpecTypeFilter = null;
                            SearchFilterType specFilterType = objectSpecType.getFilter();
                            ObjectReferenceType specOrgRef = objectSpecType.getOrgRef();
                            OrgRelationObjectSpecificationType specOrgRelation = objectSpecType.getOrgRelation();
                            RoleRelationObjectSpecificationType specRoleRelation = objectSpecType.getRoleRelation();
                            QName specTypeQName = objectSpecType.getType();
                            PrismObjectDefinition<T> objectDefinition = null;

                            // Type
                            if (specTypeQName != null) {
                                specTypeQName = prismContext.getSchemaRegistry().qualifyTypeName(specTypeQName);
                                PrismObjectDefinition<?> specObjectDef = prismContext.getSchemaRegistry()
                                        .findObjectDefinitionByType(specTypeQName);
                                if (specObjectDef == null) {
                                    throw new SchemaException("Unknown object type " + specTypeQName + " in "
                                            + autzHumanReadableDesc);
                                }
                                Class<?> specObjectClass = specObjectDef.getCompileTimeClass();
                                if (!objectType.isAssignableFrom(specObjectClass)) {
                                    LOGGER.trace(
                                            "    Authorization not applicable for object because of type mismatch, authorization {}, query {}",
                                            new Object[] { specObjectClass, objectType });
                                    continue;
                                } else {
                                    LOGGER.trace(
                                            "    Authorization is applicable for object because of type match, authorization {}, query {}",
                                            new Object[] { specObjectClass, objectType });
                                    // The spec type is a subclass of requested type. So it might be returned from the search.
                                    // We need to use type filter.
                                    objSpecTypeFilter = TypeFilter.createType(specTypeQName, null);
                                    // and now we have a more specific object definition to use later in filter processing
                                    objectDefinition = (PrismObjectDefinition<T>) specObjectDef;
                                }
                            }

                            // Owner
                            if (objectSpecType.getOwner() != null) {
                                if (objectDefinition == null) {
                                    objectDefinition = prismContext.getSchemaRegistry()
                                            .findObjectDefinitionByCompileTimeClass(objectType);
                                }
                                // TODO: MID-3899
                                if (AbstractRoleType.class.isAssignableFrom(objectType)) {
                                    objSpecSecurityFilter = applyOwnerFilterOwnerRef(
                                            new ItemPath(AbstractRoleType.F_OWNER_REF), objSpecSecurityFilter,
                                            principal, objectDefinition);
                                } else if (TaskType.class.isAssignableFrom(objectType)) {
                                    objSpecSecurityFilter = applyOwnerFilterOwnerRef(
                                            new ItemPath(TaskType.F_OWNER_REF), objSpecSecurityFilter, principal,
                                            objectDefinition);
                                } else {
                                    LOGGER.trace(
                                            "    Authorization not applicable for object because it has owner specification (this is not applicable for search)");
                                    continue;
                                }
                            }

                            //                     // Delegator
                            //                     if (objectSpecType.getDelegator() != null) {
                            //                        if (objectDefinition == null) {
                            //                           objectDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(objectType);
                            //                        }
                            //                        // TODO: MID-3899
                            //                        if (UserType.class.isAssignableFrom(objectType)) { TODO
                            //                           objSpecSecurityFilter = applyOwnerFilterOwnerRef(new ItemPath(AbstractRoleType.F_OWNER_REF), objSpecSecurityFilter,  principal, objectDefinition);
                            //                        } else if (TaskType.class.isAssignableFrom(objectType)) {
                            //                           objSpecSecurityFilter = applyOwnerFilterOwnerRef(new ItemPath(TaskType.F_OWNER_REF), objSpecSecurityFilter,  principal, objectDefinition);
                            //                        } else {
                            //                           LOGGER.trace("  Authorization not applicable for object because it has owner specification (this is not applicable for search)");
                            //                           continue;
                            //                        }
                            //                     }

                            applicable = true;

                            // Special
                            List<SpecialObjectSpecificationType> specSpecial = objectSpecType.getSpecial();
                            if (specSpecial != null && !specSpecial.isEmpty()) {
                                if (!includeSpecial) {
                                    LOGGER.trace("    Skipping authorization, because specials are present: {}",
                                            specSpecial);
                                    applicable = false;
                                }
                                if (specFilterType != null || specOrgRef != null || specOrgRelation != null
                                        || specRoleRelation != null) {
                                    throw new SchemaException(
                                            "Both filter/org/role and special object specification specified in authorization");
                                }
                                ObjectFilter specialFilter = null;
                                for (SpecialObjectSpecificationType special : specSpecial) {
                                    if (special == SpecialObjectSpecificationType.SELF) {
                                        String principalOid = principal.getOid();
                                        specialFilter = ObjectQueryUtil.filterOr(specialFilter,
                                                InOidFilter.createInOid(principalOid));
                                    } else {
                                        throw new SchemaException(
                                                "Unsupported special object specification specified in authorization: "
                                                        + special);
                                    }
                                }
                                objSpecSecurityFilter = specTypeQName != null
                                        ? TypeFilter.createType(specTypeQName, specialFilter)
                                        : specialFilter;
                            } else {
                                LOGGER.trace("    specials empty: {}", specSpecial);
                            }

                            // Filter
                            if (specFilterType != null) {
                                if (objectDefinition == null) {
                                    objectDefinition = prismContext.getSchemaRegistry()
                                            .findObjectDefinitionByCompileTimeClass(objectType);
                                }
                                ObjectFilter specFilter = parseAndEvaluateFilter(principal, objectDefinition,
                                        specFilterType, objectTargetSpec, autzHumanReadableDesc, task, result);
                                if (specFilter != null) {
                                    ObjectQueryUtil.assertNotRaw(specFilter,
                                            "Filter in authorization object has undefined items. Maybe a 'type' specification is missing in the authorization?");
                                    ObjectQueryUtil.assertPropertyOnly(specFilter,
                                            "Filter in authorization object is not property-only filter");
                                }
                                LOGGER.trace("    applying property filter {}", specFilter);
                                objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter,
                                        specFilter);
                            } else {
                                LOGGER.trace("    filter empty");
                            }

                            // Org
                            if (specOrgRef != null) {
                                ObjectFilter orgFilter = QueryBuilder.queryFor(ObjectType.class, prismContext)
                                        .isChildOf(specOrgRef.getOid()).buildFilter();
                                objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter, orgFilter);
                                LOGGER.trace("    applying org filter {}", orgFilter);
                            } else {
                                LOGGER.trace("    org empty");
                            }

                            // orgRelation
                            if (specOrgRelation != null) {
                                ObjectFilter objSpecOrgRelationFilter = null;
                                QName subjectRelation = specOrgRelation.getSubjectRelation();
                                for (ObjectReferenceType subjectParentOrgRef : principal.getUser()
                                        .getParentOrgRef()) {
                                    if (MiscSchemaUtil.compareRelation(subjectRelation,
                                            subjectParentOrgRef.getRelation())) {
                                        S_FilterEntryOrEmpty q = QueryBuilder.queryFor(ObjectType.class,
                                                prismContext);
                                        S_AtomicFilterExit q2;
                                        if (specOrgRelation.getScope() == null
                                                || specOrgRelation.getScope() == OrgScopeType.ALL_DESCENDANTS) {
                                            q2 = q.isChildOf(subjectParentOrgRef.getOid());
                                        } else if (specOrgRelation.getScope() == OrgScopeType.DIRECT_DESCENDANTS) {
                                            q2 = q.isDirectChildOf(subjectParentOrgRef.getOid());
                                        } else if (specOrgRelation.getScope() == OrgScopeType.ALL_ANCESTORS) {
                                            q2 = q.isParentOf(subjectParentOrgRef.getOid());
                                        } else {
                                            throw new UnsupportedOperationException(
                                                    "Unknown orgRelation scope " + specOrgRelation.getScope());
                                        }
                                        if (BooleanUtils.isTrue(specOrgRelation.isIncludeReferenceOrg())) {
                                            q2 = q2.or().id(subjectParentOrgRef.getOid());
                                        }
                                        objSpecOrgRelationFilter = ObjectQueryUtil
                                                .filterOr(objSpecOrgRelationFilter, q2.buildFilter());
                                    }
                                }
                                if (objSpecOrgRelationFilter == null) {
                                    objSpecOrgRelationFilter = NoneFilter.createNone();
                                }
                                objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter,
                                        objSpecOrgRelationFilter);
                                LOGGER.trace("    applying orgRelation filter {}", objSpecOrgRelationFilter);
                            } else {
                                LOGGER.trace("    orgRelation empty");
                            }

                            // roleRelation
                            if (specRoleRelation != null) {
                                ObjectFilter objSpecRoleRelationFilter = processRoleRelationFilter(principal, autz,
                                        specRoleRelation, queryItemsSpec, origFilter);
                                if (objSpecRoleRelationFilter == null) {
                                    if (autz.maySkipOnSearch()) {
                                        LOGGER.trace(
                                                "    not applying roleRelation filter {} because it is not efficient and maySkipOnSearch is set",
                                                objSpecRoleRelationFilter);
                                        applicable = false;
                                    } else {
                                        objSpecRoleRelationFilter = NoneFilter.createNone();
                                    }
                                }
                                if (objSpecRoleRelationFilter != null) {
                                    objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter,
                                            objSpecRoleRelationFilter);
                                    LOGGER.trace("  applying roleRelation filter {}", objSpecRoleRelationFilter);
                                }
                            } else {
                                LOGGER.trace("    roleRelation empty");
                            }

                            if (objSpecTypeFilter != null) {
                                objSpecTypeFilter.setFilter(objSpecSecurityFilter);
                                objSpecSecurityFilter = objSpecTypeFilter;
                            }

                            traceFilter("objSpecSecurityFilter", objectSpecType, objSpecSecurityFilter);
                            autzObjSecurityFilter = ObjectQueryUtil.filterOr(autzObjSecurityFilter,
                                    objSpecSecurityFilter);
                        }

                    }

                    traceFilter("autzObjSecurityFilter", autz, autzObjSecurityFilter);

                    if (applicable) {
                        autzObjSecurityFilter = ObjectQueryUtil.simplify(autzObjSecurityFilter);
                        // authority is applicable to this situation. now we can process the decision.
                        AuthorizationDecisionType decision = autz.getDecision();
                        if (decision == null || decision == AuthorizationDecisionType.ALLOW) {
                            // allow
                            if (ObjectQueryUtil.isAll(autzObjSecurityFilter)) {
                                // this is "allow all" authorization.
                                hasAllowAll = true;
                            } else {
                                securityFilterAllow = ObjectQueryUtil.filterOr(securityFilterAllow,
                                        autzObjSecurityFilter);
                            }
                            if (!ObjectQueryUtil.isNone(autzObjSecurityFilter)) {
                                queryItemsSpec.collectItems(autz);
                            }
                        } else {
                            // deny
                            if (autz.hasItemSpecification()) {
                                // This is a tricky situation. We have deny authorization, but it only denies access to
                                // some items. Therefore we need to find the objects and then filter out the items.
                                // Therefore do not add this authorization into the filter.
                            } else {
                                if (ObjectQueryUtil.isAll(autzObjSecurityFilter)) {
                                    // This is "deny all". We cannot have anything stronger than that.
                                    // There is no point in continuing the evaluation.
                                    if (LOGGER.isTraceEnabled()) {
                                        LOGGER.trace("AUTZ {} done: principal={}, operation={}, phase={}: deny all",
                                                desc, getUsername(principal), prettyActionUrl(operationUrl), phase);
                                    }
                                    NoneFilter secFilter = NoneFilter.createNone();
                                    traceFilter("secFilter", null, secFilter);
                                    return secFilter;
                                }
                                securityFilterDeny = ObjectQueryUtil.filterOr(securityFilterDeny,
                                        autzObjSecurityFilter);
                            }
                        }
                    }

                    traceFilter("securityFilterAllow", autz, securityFilterAllow);
                    traceFilter("securityFilterDeny", autz, securityFilterDeny);

                } else {
                    LOGGER.warn("Unknown authority type {} in user {}", authority.getClass(),
                            getUsername(principal));
                }
            }
        }

        traceFilter("securityFilterAllow", null, securityFilterAllow);
        traceFilter("securityFilterDeny", null, securityFilterDeny);

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace(" Final items: {}", queryItemsSpec.shortDump());
        }
        List<ItemPath> unsatisfiedItems = queryItemsSpec.evaluateUnsatisfierItems();
        if (!unsatisfiedItems.isEmpty()) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(
                        "AUTZ {} done: principal={}, operation={}, phase={}: deny because items {} are not allowed",
                        desc, getUsername(principal), prettyActionUrl(operationUrl), phase, unsatisfiedItems);
            }
            NoneFilter secFilter = NoneFilter.createNone();
            traceFilter("secFilter", null, secFilter);
            return secFilter;
        }

        ObjectFilter origWithAllowFilter;
        if (hasAllowAll) {
            origWithAllowFilter = origFilter;
        } else if (securityFilterAllow == null) {
            // Nothing has been allowed. This means default deny.
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("AUTZ {} done: principal={}, operation={}, phase={}: default deny", desc,
                        getUsername(principal), prettyActionUrl(operationUrl), phase);
            }
            NoneFilter secFilter = NoneFilter.createNone();
            traceFilter("secFilter", null, secFilter);
            return secFilter;
        } else {
            origWithAllowFilter = ObjectQueryUtil.filterAnd(origFilter, securityFilterAllow);
        }

        if (securityFilterDeny == null) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("AUTZ {} done: principal={}, operation={}, phase={}: allow\n  Filter:\n{}", desc,
                        getUsername(principal), prettyActionUrl(operationUrl), phase,
                        origWithAllowFilter == null ? "null" : origWithAllowFilter.debugDump(2));
            }
            traceFilter("origWithAllowFilter", null, origWithAllowFilter);
            return origWithAllowFilter;
        } else {
            ObjectFilter secFilter = ObjectQueryUtil.filterAnd(origWithAllowFilter,
                    NotFilter.createNot(securityFilterDeny));
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(
                        "AUTZ {} done: principal={}, operation={}, phase={}: allow (with deny clauses)\n  Filter:\n{}",
                        desc, getUsername(principal), prettyActionUrl(operationUrl), phase,
                        secFilter == null ? "null" : secFilter.debugDump(2));
            }
            traceFilter("secFilter", null, secFilter);
            return secFilter;
        }
    }

    private boolean isApplicableLimitations(Authorization autz, String limitAuthorizationAction) {
        if (limitAuthorizationAction == null) {
            return true;
        }
        AuthorizationLimitationsType limitations = autz.getLimitations();
        if (limitations == null) {
            return true;
        }
        List<String> limitationsActions = limitations.getAction();
        if (limitationsActions.isEmpty()) {
            return true;
        }
        return limitationsActions.contains(limitAuthorizationAction);
    }

    private boolean isApplicableRelation(Authorization autz, QName requestRelation) {
        List<QName> autzRelation = autz.getRelation();
        if (autzRelation == null || autzRelation.isEmpty()) {
            return true;
        }
        return QNameUtil.contains(autzRelation, requestRelation);
    }

    /**
     * Very rudimentary and experimental implementation.
     */
    private ObjectFilter processRoleRelationFilter(MidPointPrincipal principal, Authorization autz,
            RoleRelationObjectSpecificationType specRoleRelation, AutzItemPaths queryItemsSpec,
            ObjectFilter origFilter) {
        ObjectFilter refRoleFilter = null;
        if (BooleanUtils.isTrue(specRoleRelation.isIncludeReferenceRole())) {
            // This could mean that we will need to add filters for all roles in
            // subject's roleMembershipRef. There may be thousands of these.
            if (!autz.maySkipOnSearch()) {
                throw new UnsupportedOperationException(
                        "Inefficient roleRelation search (includeReferenceRole=true) is not supported yet");
            }
        }

        ObjectFilter membersFilter = null;
        if (!BooleanUtils.isFalse(specRoleRelation.isIncludeMembers())) {
            List<PrismReferenceValue> queryRoleRefs = getRoleOidsFromFilter(origFilter);
            if (queryRoleRefs == null || queryRoleRefs.isEmpty()) {
                // Cannot find specific role OID in original query. This could mean that we
                // will need to add filters for all roles in subject's roleMembershipRef.
                // There may be thousands of these.
                if (!autz.maySkipOnSearch()) {
                    throw new UnsupportedOperationException(
                            "Inefficient roleRelation search (includeMembers=true without role in the original query) is not supported yet");
                }
            } else {
                QName subjectRelation = specRoleRelation.getSubjectRelation();
                boolean isRoleOidOk = false;
                for (ObjectReferenceType subjectRoleMembershipRef : principal.getUser().getRoleMembershipRef()) {
                    if (!MiscSchemaUtil.compareRelation(subjectRelation, subjectRoleMembershipRef.getRelation())) {
                        continue;
                    }
                    if (!PrismReferenceValue.containsOid(queryRoleRefs, subjectRoleMembershipRef.getOid())) {
                        continue;
                    }
                    isRoleOidOk = true;
                    break;
                }
                if (isRoleOidOk) {
                    // There is already a good filter in the origFilter
                    // TODO: mind the objectRelation
                    membersFilter = AllFilter.createAll();
                } else {
                    membersFilter = NoneFilter.createNone();
                }
            }
        }

        return ObjectQueryUtil.filterOr(refRoleFilter, membersFilter);
    }

    private List<PrismReferenceValue> getRoleOidsFromFilter(ObjectFilter origFilter) {
        if (origFilter == null) {
            return null;
        }
        if (origFilter instanceof RefFilter) {
            ItemPath path = ((RefFilter) origFilter).getPath();
            if (path.equals(SchemaConstants.PATH_ROLE_MEMBERSHIP_REF)) {
                return ((RefFilter) origFilter).getValues();
            }
        }
        if (origFilter instanceof AndFilter) {
            for (ObjectFilter condition : ((AndFilter) origFilter).getConditions()) {
                List<PrismReferenceValue> refs = getRoleOidsFromFilter(condition);
                if (refs != null && !refs.isEmpty()) {
                    return refs;
                }
            }
        }
        return null;
    }

    private <T extends ObjectType> ObjectFilter applyOwnerFilterOwnerRef(ItemPath ownerRefPath,
            ObjectFilter objSpecSecurityFilter, MidPointPrincipal principal,
            PrismObjectDefinition<T> objectDefinition) {
        PrismReferenceDefinition ownerRefDef = objectDefinition.findReferenceDefinition(ownerRefPath);
        S_AtomicFilterExit builder = QueryBuilder.queryFor(AbstractRoleType.class, prismContext)
                .item(ownerRefPath, ownerRefDef).ref(principal.getUser().getOid());
        // TODO don't understand this code
        for (ObjectReferenceType subjectParentOrgRef : principal.getUser().getParentOrgRef()) {
            if (ObjectTypeUtil.isDefaultRelation(subjectParentOrgRef.getRelation())) {
                builder = builder.or().item(ownerRefPath, ownerRefDef).ref(subjectParentOrgRef.getOid());
            }
        }
        ObjectFilter objSpecOwnerFilter = builder.buildFilter();
        objSpecSecurityFilter = ObjectQueryUtil.filterAnd(objSpecSecurityFilter, objSpecOwnerFilter);
        LOGGER.trace("  applying owner filter {}", objSpecOwnerFilter);
        return objSpecSecurityFilter;
    }

    private void traceFilter(String message, Object forObj, ObjectFilter filter) {
        if (FILTER_TRACE_ENABLED) {
            LOGGER.trace("FILTER {} for {}:\n{}", message, forObj, filter == null ? null : filter.debugDump(1));
        }

    }

    private String getUsername(MidPointPrincipal principal) {
        return principal == null ? null : principal.getUsername();
    }

    private String prettyActionUrl(String fullUrl) {
        return DebugUtil.shortenUrl(AuthorizationConstants.NS_SECURITY_PREFIX, fullUrl);
    }

    private <O extends ObjectType> String getObjectType(Class<O> type) {
        if (type == null) {
            return null;
        }
        return type.getSimpleName();
    }

    @Override
    public <O extends ObjectType, R extends AbstractRoleType> ItemSecurityConstraints getAllowedRequestAssignmentItems(
            MidPointPrincipal midPointPrincipal, String operationUrl, PrismObject<O> object, PrismObject<R> target,
            OwnerResolver ownerResolver, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {

        ItemSecurityConstraintsImpl itemConstraints = new ItemSecurityConstraintsImpl();

        for (Authorization autz : getAuthorities(midPointPrincipal)) {
            String autzHumanReadableDesc = autz.getHumanReadableDesc();
            LOGGER.trace("  Evaluating {}", autzHumanReadableDesc);

            // First check if the authorization is applicable.

            // action
            if (!autz.getAction().contains(operationUrl)
                    && !autz.getAction().contains(AuthorizationConstants.AUTZ_ALL_URL)) {
                LOGGER.trace("    {} not applicable for operation {}", autzHumanReadableDesc, operationUrl);
                continue;
            }

            // phase
            if (autz.getPhase() != null && autz.getPhase() != AuthorizationPhaseType.REQUEST) {
                LOGGER.trace("    {} is not applicable for phase {} (breaking evaluation)", autzHumanReadableDesc,
                        AuthorizationPhaseType.REQUEST);
                continue;
            }

            // object
            if (isApplicable(autz.getObject(), object, midPointPrincipal, ownerResolver, "object",
                    autzHumanReadableDesc, task, result)) {
                LOGGER.trace("    {} applicable for object {} (continuing evaluation)", autzHumanReadableDesc,
                        object);
            } else {
                LOGGER.trace(
                        "    {} not applicable for object {}, none of the object specifications match (breaking evaluation)",
                        autzHumanReadableDesc, object);
                continue;
            }

            // target
            if (isApplicable(autz.getTarget(), target, midPointPrincipal, ownerResolver, "target",
                    autzHumanReadableDesc, task, result)) {
                LOGGER.trace("    {} applicable for target {} (continuing evaluation)", autzHumanReadableDesc,
                        object);
            } else {
                LOGGER.trace(
                        "    {} not applicable for target {}, none of the target specifications match (breaking evaluation)",
                        autzHumanReadableDesc, object);
                continue;
            }

            // authority is applicable to this situation. now we can process the decision.
            itemConstraints.collectItems(autz);
        }

        return itemConstraints;
    }

    @Override
    public MidPointPrincipal createDonorPrincipal(MidPointPrincipal attorneyPrincipal,
            String attorneyAuthorizationAction, PrismObject<UserType> donor, Task task, OperationResult result)
            throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException,
            ConfigurationException, SecurityViolationException {
        if (attorneyPrincipal.getAttorney() != null) {
            throw new UnsupportedOperationException("Transitive attorney is not supported yet");
        }

        AuthorizationLimitationsCollector limitationsCollector = new AuthorizationLimitationsCollector();
        AuthorizationParameters<UserType, ObjectType> autzParams = AuthorizationParameters.Builder
                .buildObject(donor);
        AccessDecision decision = isAuthorizedInternal(attorneyPrincipal, attorneyAuthorizationAction, null,
                autzParams, null, limitationsCollector, task, result);
        if (!decision.equals(AccessDecision.ALLOW)) {
            failAuthorization(attorneyAuthorizationAction, null, autzParams, result);
        }

        MidPointPrincipal donorPrincipal = securityContextManager.getUserProfileService().getPrincipal(donor,
                limitationsCollector, result);
        donorPrincipal.setAttorney(attorneyPrincipal.getUser());

        // chain principals so we can easily drop the power of attorney and return back to original identity
        donorPrincipal.setPreviousPrincipal(attorneyPrincipal);

        return donorPrincipal;
    }

    @Override
    public <O extends ObjectType> AccessDecision determineSubitemDecision(
            ObjectSecurityConstraints securityConstraints, ObjectDelta<O> delta, PrismObject<O> currentObject,
            String operationUrl, AuthorizationPhaseType phase, ItemPath subitemRootPath) {
        return determineDeltaDecision(delta, currentObject, (nameOnlyItemPath, removingContainer) -> {
            if (removingContainer && isInList(nameOnlyItemPath,
                    AuthorizationConstants.OPERATIONAL_ITEMS_ALLOWED_FOR_CONTAINER_DELETE)) {
                return null;
            }
            if (AuthorizationPhaseType.EXECUTION.equals(phase)
                    && isInList(nameOnlyItemPath, AuthorizationConstants.EXECUTION_ITEMS_ALLOWED_BY_DEFAULT)) {
                return null;
            }
            if (subitemRootPath != null && !subitemRootPath.isSubPathOrEquivalent(nameOnlyItemPath)) {
                //                  LOGGER.trace("subitem decision: {} <=> {} (not under root) : {}", subitemRootPath, nameOnlyItemPath, null);
                return null;
            }

            AuthorizationDecisionType authorizationDecisionType = securityConstraints
                    .findItemDecision(nameOnlyItemPath, operationUrl, phase);
            AccessDecision decision = AccessDecision.translate(authorizationDecisionType);
            //               LOGGER.trace("subitem decision: {} <=> {} : {}", subitemRootPath, nameOnlyItemPath, decision);
            return decision;
        });
    }

    @Override
    public <C extends Containerable> AccessDecision determineSubitemDecision(
            ObjectSecurityConstraints securityConstraints, PrismContainerValue<C> containerValue,
            String operationUrl, AuthorizationPhaseType phase, ItemPath subitemRootPath,
            PlusMinusZero plusMinusZero, String decisionContextDesc) {
        boolean removingContainer = false;
        if (plusMinusZero == PlusMinusZero.MINUS) {
            removingContainer = true;
        }
        return determineContainerDecision(containerValue, (nameOnlyItemPath, lRemovingContainer) -> {
            if (lRemovingContainer && isInList(nameOnlyItemPath,
                    AuthorizationConstants.OPERATIONAL_ITEMS_ALLOWED_FOR_CONTAINER_DELETE)) {
                return null;
            }
            if (AuthorizationPhaseType.EXECUTION.equals(phase)
                    && isInList(nameOnlyItemPath, AuthorizationConstants.EXECUTION_ITEMS_ALLOWED_BY_DEFAULT)) {
                return null;
            }
            if (subitemRootPath != null && !subitemRootPath.isSubPathOrEquivalent(nameOnlyItemPath)) {
                //                  LOGGER.trace("subitem decision: {} <=> {} (not under root) : {}", subitemRootPath, nameOnlyItemPath, null);
                return null;
            }

            AuthorizationDecisionType authorizationDecisionType = securityConstraints
                    .findItemDecision(nameOnlyItemPath, operationUrl, phase);
            AccessDecision decision = AccessDecision.translate(authorizationDecisionType);
            //               LOGGER.trace("subitem decision: {} <=> {} : {}", subitemRootPath, nameOnlyItemPath, decision);
            return decision;
        }, removingContainer, decisionContextDesc);
    }
}