/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.apache.wss4j.policy.stax.enforcer;

import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

import org.apache.neethi.Assertion;
import org.apache.neethi.ExactlyOne;
import org.apache.neethi.Policy;
import org.apache.neethi.PolicyComponent;
import org.apache.neethi.PolicyContainingAssertion;
import org.apache.neethi.PolicyOperator;
import org.apache.neethi.builders.PrimitiveAssertion;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.policy.SPConstants;
import org.apache.wss4j.common.WSSPolicyException;
import org.apache.wss4j.policy.SPConstants.IncludeTokenType;
import org.apache.wss4j.policy.model.AbstractBinding;
import org.apache.wss4j.policy.model.AbstractSecurityAssertion;
import org.apache.wss4j.policy.model.AbstractSymmetricAsymmetricBinding;
import org.apache.wss4j.policy.model.AbstractToken;
import org.apache.wss4j.policy.model.AlgorithmSuite;
import org.apache.wss4j.policy.model.ContentEncryptedElements;
import org.apache.wss4j.policy.model.EncryptedElements;
import org.apache.wss4j.policy.model.EncryptedParts;
import org.apache.wss4j.policy.model.HttpsToken;
import org.apache.wss4j.policy.model.IssuedToken;
import org.apache.wss4j.policy.model.KerberosToken;
import org.apache.wss4j.policy.model.KeyValueToken;
import org.apache.wss4j.policy.model.Layout;
import org.apache.wss4j.policy.model.RelToken;
import org.apache.wss4j.policy.model.RequiredElements;
import org.apache.wss4j.policy.model.RequiredParts;
import org.apache.wss4j.policy.model.SamlToken;
import org.apache.wss4j.policy.model.SecureConversationToken;
import org.apache.wss4j.policy.model.SecurityContextToken;
import org.apache.wss4j.policy.model.SignedElements;
import org.apache.wss4j.policy.model.SignedParts;
import org.apache.wss4j.policy.model.SpnegoContextToken;
import org.apache.wss4j.policy.model.SupportingTokens;
import org.apache.wss4j.policy.model.Trust10;
import org.apache.wss4j.policy.model.Trust13;
import org.apache.wss4j.policy.model.UsernameToken;
import org.apache.wss4j.policy.model.Wss10;
import org.apache.wss4j.policy.model.X509Token;
import org.apache.wss4j.policy.model.Wss11;
import org.apache.wss4j.policy.stax.Assertable;
import org.apache.wss4j.policy.stax.DummyPolicyAsserter;
import org.apache.wss4j.policy.stax.OperationPolicy;
import org.apache.wss4j.policy.stax.PolicyAsserter;
import org.apache.wss4j.policy.stax.PolicyViolationException;
import org.apache.wss4j.policy.stax.assertionStates.AlgorithmSuiteAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.ContentEncryptedElementsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.EncryptedElementsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.EncryptedPartsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.HttpsTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.IncludeTimeStampAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.IssuedTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.KerberosTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.KeyValueTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.OnlySignEntireHeadersAndBodyAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.ProtectionOrderAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.RelTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.RequiredElementsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.RequiredPartsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SamlTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SecureConversationTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SecurityContextTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SignatureConfirmationAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SignatureProtectionAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SignedElementsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SignedPartsAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.SpnegoContextTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.TokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.TokenProtectionAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.UsernameTokenAssertionState;
import org.apache.wss4j.policy.stax.assertionStates.X509TokenAssertionState;
import org.apache.wss4j.stax.ext.WSSConstants;
import org.apache.wss4j.stax.securityEvent.NoSecuritySecurityEvent;
import org.apache.wss4j.stax.securityEvent.OperationSecurityEvent;
import org.apache.wss4j.stax.securityEvent.WSSecurityEventConstants;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.stax.securityEvent.SecurityEvent;
import org.apache.xml.security.stax.securityEvent.SecurityEventConstants;
import org.apache.xml.security.stax.securityEvent.SecurityEventListener;

/**
 * The PolicyEnforcer verifies the Policy assertions
 * The Assertion will be validated in realtime as far as possible
 */
public class PolicyEnforcer implements SecurityEventListener {

    //todo:
    // AlgorithmSuite SoapNorm
    // AlgorithmSuite STR Trans
    // AlgorithmSuite XPATH
    // AlgorithmSuite Comp Key
    // Layout? I don't know if it is that relevant and worth. We need security header element numbering
    //to implement it.
    // HttpsToken Algorithms
    //unused tokens must be checked (algorithms etc)

    private static final transient org.slf4j.Logger LOG =
        org.slf4j.LoggerFactory.getLogger(PolicyEnforcer.class);

    private static final QName SOAP11_FAULT = new QName(WSSConstants.NS_SOAP11, "Fault");
    private static final QName SOAP12_FAULT = new QName(WSSConstants.NS_SOAP12, "Fault");

    private final List<OperationPolicy> operationPolicies;
    private OperationPolicy effectivePolicy;
    private final List<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMap;
    private final List<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> failedAssertionStateMap;

    private final Deque<SecurityEvent> securityEventQueue = new LinkedList<>();
    private boolean operationSecurityEventOccured = false;
    private boolean initiator;
    private String actorOrRole;
    private int attachmentCount;
    private boolean noSecurityHeader;
    private boolean faultOccurred;
    private final PolicyAsserter policyAsserter;
    private boolean soap12;

    public PolicyEnforcer(List<OperationPolicy> operationPolicies, String soapAction, boolean initiator,
                          String actorOrRole, int attachmentCount, PolicyAsserter policyAsserter, boolean soap12
    ) throws WSSPolicyException {
        this.operationPolicies = operationPolicies;
        this.initiator = initiator;
        this.actorOrRole = actorOrRole;
        this.attachmentCount = attachmentCount;
        this.soap12 = soap12;
        assertionStateMap = new LinkedList<>();
        failedAssertionStateMap = new LinkedList<>();

        if (policyAsserter == null) {
            this.policyAsserter = new DummyPolicyAsserter();
        } else {
            this.policyAsserter = policyAsserter;
        }

        if (soapAction != null && !soapAction.isEmpty()) {
            effectivePolicy = findPolicyBySOAPAction(operationPolicies, soapAction);
            if (effectivePolicy != null) {
                buildAssertionStateMap(effectivePolicy.getPolicy(), assertionStateMap);
            }
        }
    }

    private OperationPolicy findPolicyBySOAPAction(List<OperationPolicy> operationPolicies, String soapAction) {
        Iterator<OperationPolicy> operationPolicyIterator = operationPolicies.iterator();
        while (operationPolicyIterator.hasNext()) {
            OperationPolicy operationPolicy = operationPolicyIterator.next();
            if (soapAction.equals(operationPolicy.getOperationAction())) {
                return operationPolicy;
            }
        }
        return null;
    }

    private OperationPolicy findPolicyBySOAPOperationName(List<OperationPolicy> operationPolicies,
                                                          QName soapOperationName) {
        Iterator<OperationPolicy> operationPolicyIterator = operationPolicies.iterator();
        OperationPolicy noNamespaceOperation = null;

        while (operationPolicyIterator.hasNext()) {
            OperationPolicy operationPolicy = operationPolicyIterator.next();
            if (operationPolicy.getOperationName() != null) {
                if (soapOperationName.equals(operationPolicy.getOperationName())) {
                    return operationPolicy;
                } else if ("".equals(operationPolicy.getOperationName().getNamespaceURI())
                    && soapOperationName.getLocalPart().equals(
                        operationPolicy.getOperationName().getLocalPart())) {
                    noNamespaceOperation = operationPolicy;
                }
            }
        }

        return noNamespaceOperation;
    }

    /**
     * Precondition: Policy _must_ be normalized!
     */
    private void buildAssertionStateMap(
            PolicyComponent policyComponent,
            List<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMap
    ) throws WSSPolicyException {
        if (policyComponent instanceof PolicyOperator) {
            PolicyOperator policyOperator = (PolicyOperator) policyComponent;
            List<PolicyComponent> policyComponents = policyOperator.getPolicyComponents();
            int alternative = 0;
            Iterator<PolicyComponent> policyComponentIterator = policyComponents.iterator();
            while (policyComponentIterator.hasNext()) {
                PolicyComponent curPolicyComponent = policyComponentIterator.next();
                if (policyOperator instanceof ExactlyOne) {
                    assertionStateMap.add(new HashMap<SecurityEventConstants.Event,
                                          Map<Assertion, List<Assertable>>>());
                    buildAssertionStateMap(curPolicyComponent, assertionStateMap, alternative++);
                } else {
                    buildAssertionStateMap(curPolicyComponent, assertionStateMap);
                }
            }
        } else {
            throw new WSSPolicyException("Invalid PolicyComponent: " + policyComponent
                                         + " " + policyComponent.getType());
        }
    }

    private void buildAssertionStateMap(
            PolicyComponent policyComponent,
            List<Map<SecurityEventConstants.Event,
            Map<Assertion, List<Assertable>>>> assertionStateMap,
            int alternative
    ) throws WSSPolicyException {
        if (policyComponent instanceof PolicyOperator) {
            PolicyOperator policyOperator = (PolicyOperator) policyComponent;
            List<PolicyComponent> policyComponents = policyOperator.getPolicyComponents();
            Iterator<PolicyComponent> policyComponentIterator = policyComponents.iterator();
            while (policyComponentIterator.hasNext()) {
                PolicyComponent curPolicyComponent = policyComponentIterator.next();
                buildAssertionStateMap(curPolicyComponent, assertionStateMap, alternative);
            }
        } else if (policyComponent instanceof AbstractSecurityAssertion) {
            AbstractSecurityAssertion abstractSecurityAssertion = (AbstractSecurityAssertion) policyComponent;

            List<Assertable> assertablesList = getAssertableForAssertion(abstractSecurityAssertion);
            Iterator<Assertable> assertableIterator = assertablesList.iterator();
            while (assertableIterator.hasNext()) {
                Assertable assertable = assertableIterator.next();
                final Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> map =
                    assertionStateMap.get(alternative);
                final SecurityEventConstants.Event[] securityEventType = assertable.getSecurityEventType();
                for (int j = 0; j < securityEventType.length; j++) {
                    SecurityEventConstants.Event event = securityEventType[j];
                    Map<Assertion, List<Assertable>> assertables = map.get(event);
                    if (assertables == null) {
                        assertables = new HashMap<>();
                        map.put(event, assertables);
                    }
                    addAssertionState(assertables, abstractSecurityAssertion, assertable);
                }
            }
            if (abstractSecurityAssertion instanceof PolicyContainingAssertion) {
                buildAssertionStateMap(((PolicyContainingAssertion) abstractSecurityAssertion).getPolicy(),
                                       assertionStateMap, alternative);
            }
        } else if (!(policyComponent instanceof PrimitiveAssertion)) {
            throw new WSSPolicyException("Unsupported PolicyComponent: " + policyComponent
                                         + " type: " + policyComponent.getType());
        }
    }

    private void addAssertionState(Map<Assertion, List<Assertable>> assertables,
                                   Assertion keyAssertion,
                                   Assertable assertable) {
        List<Assertable> assertableList = assertables.get(keyAssertion);
        if (assertableList == null) {
            assertableList = new LinkedList<>();
            assertables.put(keyAssertion, assertableList);
        }
        assertableList.add(assertable);
    }

    // Don't return a Token that is not required
    private boolean isTokenRequired(AbstractToken token) {
        SPConstants.IncludeTokenType includeTokenType = token.getIncludeTokenType();
        if (includeTokenType == IncludeTokenType.INCLUDE_TOKEN_NEVER) {
            return false;
        } else if (initiator && includeTokenType == IncludeTokenType.INCLUDE_TOKEN_ALWAYS_TO_RECIPIENT) {
            return false;
        } else if (initiator && includeTokenType == IncludeTokenType.INCLUDE_TOKEN_ONCE) {
            return false;
        } else if (!initiator && includeTokenType == IncludeTokenType.INCLUDE_TOKEN_ALWAYS_TO_INITIATOR) {
            return false;
        }
        return true;
    }

    protected List<Assertable> getAssertableForAssertion(AbstractSecurityAssertion abstractSecurityAssertion)
        throws WSSPolicyException {
        List<Assertable> assertableList = new LinkedList<>();
        boolean tokenRequired = true;
        if (abstractSecurityAssertion instanceof AbstractToken) {
            tokenRequired = isTokenRequired((AbstractToken)abstractSecurityAssertion);
        }

        if (abstractSecurityAssertion instanceof ContentEncryptedElements) {
            // initialized with asserted=true because it could be that parent elements are encrypted and
            // therefore these element are also encrypted
            // the test if it is really encrypted is done via the PolicyInputProcessor which emits
            // EncryptedElementEvents for unencrypted elements with the unencrypted flag
            assertableList.add(new ContentEncryptedElementsAssertionState(abstractSecurityAssertion, policyAsserter, true));
        } else if (abstractSecurityAssertion instanceof EncryptedParts) {
            // initialized with asserted=true with the same reason as by the EncryptedParts above
            assertableList.add(new EncryptedPartsAssertionState(abstractSecurityAssertion, policyAsserter, true, attachmentCount, soap12));
        } else if (abstractSecurityAssertion instanceof EncryptedElements) {
            // initialized with asserted=true with the same reason as by the EncryptedParts above
            assertableList.add(new EncryptedElementsAssertionState(abstractSecurityAssertion, policyAsserter, true));
        } else if (abstractSecurityAssertion instanceof SignedParts) {
            // initialized with asserted=true because it could be that parent elements are signed and
            // therefore these element are also signed
            // the test if it is really signed is done via the PolicyInputProcessor which emits SignedElementEvents for
            // unsigned elements with the unsigned flag
            assertableList.add(new SignedPartsAssertionState(abstractSecurityAssertion, policyAsserter, true, attachmentCount, soap12));
        } else if (abstractSecurityAssertion instanceof SignedElements) {
            // initialized with asserted=true with the same reason as by the SignedParts above
            assertableList.add(new SignedElementsAssertionState(abstractSecurityAssertion, policyAsserter, true));
        } else if (abstractSecurityAssertion instanceof RequiredElements) {
            assertableList.add(new RequiredElementsAssertionState(abstractSecurityAssertion, policyAsserter, false));
        } else if (abstractSecurityAssertion instanceof RequiredParts) {
            assertableList.add(new RequiredPartsAssertionState(abstractSecurityAssertion, policyAsserter, false, soap12));
        } else if (abstractSecurityAssertion instanceof UsernameToken) {
            assertableList.add(new UsernameTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof IssuedToken) {
            assertableList.add(new IssuedTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof X509Token) {
            assertableList.add(new X509TokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof KerberosToken) {
            assertableList.add(new KerberosTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof SpnegoContextToken) {
            assertableList.add(new SpnegoContextTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof SecureConversationToken) {
            assertableList.add(new SecureConversationTokenAssertionState(abstractSecurityAssertion, !tokenRequired,
                                                                         policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof SecurityContextToken) {
            assertableList.add(new SecurityContextTokenAssertionState(abstractSecurityAssertion, !tokenRequired,
                                                                      policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof SamlToken) {
            assertableList.add(new SamlTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof RelToken) {
            assertableList.add(new RelTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof HttpsToken) {
            assertableList.add(new HttpsTokenAssertionState(abstractSecurityAssertion,
                                                            !tokenRequired || initiator, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof KeyValueToken) {
            assertableList.add(new KeyValueTokenAssertionState(abstractSecurityAssertion, !tokenRequired, policyAsserter, initiator));
        } else if (abstractSecurityAssertion instanceof AlgorithmSuite) {
            // initialized with asserted=true because we do negative matching
            assertableList.add(new AlgorithmSuiteAssertionState(abstractSecurityAssertion, policyAsserter, true));
        } else if (abstractSecurityAssertion instanceof Layout) {
            //assertableList.add(new LayoutAssertionState(abstractSecurityAssertion, true));
            String namespace = abstractSecurityAssertion.getName().getNamespaceURI();
            policyAsserter.assertPolicy(new QName(namespace, SPConstants.LAYOUT_LAX));
            policyAsserter.assertPolicy(new QName(namespace, SPConstants.LAYOUT_LAX_TIMESTAMP_FIRST));
            policyAsserter.assertPolicy(new QName(namespace, SPConstants.LAYOUT_LAX_TIMESTAMP_LAST));
            policyAsserter.assertPolicy(new QName(namespace, SPConstants.LAYOUT_STRICT));
            policyAsserter.assertPolicy(abstractSecurityAssertion);
        } else if (abstractSecurityAssertion instanceof AbstractBinding) {
            policyAsserter.assertPolicy(abstractSecurityAssertion);
            AbstractBinding abstractBinding = (AbstractBinding) abstractSecurityAssertion;
            if (abstractBinding instanceof AbstractSymmetricAsymmetricBinding) {
                AbstractSymmetricAsymmetricBinding abstractSymmetricAsymmetricBinding =
                    (AbstractSymmetricAsymmetricBinding) abstractSecurityAssertion;
                assertableList.add(new ProtectionOrderAssertionState(abstractSymmetricAsymmetricBinding, policyAsserter, true));
                assertableList.add(new SignatureProtectionAssertionState(abstractSymmetricAsymmetricBinding, policyAsserter, true));
                if (abstractSymmetricAsymmetricBinding.isOnlySignEntireHeadersAndBody()) {
                    //initialized with asserted=true because we do negative matching
                    assertableList.add(new OnlySignEntireHeadersAndBodyAssertionState(abstractSecurityAssertion, policyAsserter,
                                                                                      true, actorOrRole));
                }
                assertableList.add(new TokenProtectionAssertionState(abstractSecurityAssertion, policyAsserter, true, soap12));
            }

            //WSP1.3, 6.2 Timestamp Property
            assertableList.add(new IncludeTimeStampAssertionState(abstractBinding, policyAsserter, true));
            if (abstractBinding.isIncludeTimestamp()) {
                List<QName> timestampElementPath = new LinkedList<>();
                if (soap12) {
                    timestampElementPath.addAll(WSSConstants.SOAP_12_WSSE_SECURITY_HEADER_PATH);
                } else {
                    timestampElementPath.addAll(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
                }
                timestampElementPath.add(WSSConstants.TAG_WSU_TIMESTAMP);
                RequiredElementsAssertionState requiredElementsAssertionState =
                    new RequiredElementsAssertionState(abstractBinding, policyAsserter, false);
                requiredElementsAssertionState.addElement(timestampElementPath);
                assertableList.add(requiredElementsAssertionState);

                SignedElementsAssertionState signedElementsAssertionState =
                    new SignedElementsAssertionState(abstractSecurityAssertion, policyAsserter, true);
                signedElementsAssertionState.addElement(timestampElementPath);
                assertableList.add(signedElementsAssertionState);
            }
        } else if (abstractSecurityAssertion instanceof Wss10) {
            Wss10 wss10 = (Wss10)abstractSecurityAssertion;
            String namespace = wss10.getName().getNamespaceURI();
            policyAsserter.assertPolicy(abstractSecurityAssertion);

            if (wss10.isMustSupportRefEmbeddedToken()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_REF_EMBEDDED_TOKEN));
            }
            if (wss10.isMustSupportRefExternalURI()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_REF_EXTERNAL_URI));
            }
            if (wss10.isMustSupportRefIssuerSerial()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_REF_ISSUER_SERIAL));
            }
            if (wss10.isMustSupportRefKeyIdentifier()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_REF_KEY_IDENTIFIER));
            }

            if (abstractSecurityAssertion instanceof Wss11) {
                Wss11 wss11 = (Wss11)abstractSecurityAssertion;
                if (wss11.isMustSupportRefEncryptedKey()) {
                    policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_REF_ENCRYPTED_KEY));
                }
                if (wss11.isMustSupportRefThumbprint()) {
                    policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_REF_THUMBPRINT));
                }
                if (wss11.isRequireSignatureConfirmation()) {
                    assertableList.add(new SignatureConfirmationAssertionState(wss11, policyAsserter, true));
                    if (initiator) {
                        //9 WSS: SOAP Message Security Options [Signature Confirmation]
                        List<QName> signatureConfirmationElementPath = new LinkedList<>();
                        if (soap12) {
                            signatureConfirmationElementPath.addAll(WSSConstants.SOAP_12_WSSE_SECURITY_HEADER_PATH);
                        } else {
                            signatureConfirmationElementPath.addAll(WSSConstants.SOAP_11_WSSE_SECURITY_HEADER_PATH);
                        }
                        signatureConfirmationElementPath.add(WSSConstants.TAG_WSSE11_SIG_CONF);
                        RequiredElementsAssertionState requiredElementsAssertionState =
                            new RequiredElementsAssertionState(wss11, policyAsserter, false);
                        requiredElementsAssertionState.addElement(signatureConfirmationElementPath);
                        assertableList.add(requiredElementsAssertionState);

                        SignedElementsAssertionState signedElementsAssertionState =
                            new SignedElementsAssertionState(wss11, policyAsserter, true);
                        signedElementsAssertionState.addElement(signatureConfirmationElementPath);
                        assertableList.add(signedElementsAssertionState);
                    }
                }
            }
        } else if (abstractSecurityAssertion instanceof Trust10) {
            Trust10 trust10 = (Trust10)abstractSecurityAssertion;
            String namespace = trust10.getName().getNamespaceURI();
            policyAsserter.assertPolicy(abstractSecurityAssertion);

            if (trust10.isMustSupportClientChallenge()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_CLIENT_CHALLENGE));
            }
            if (trust10.isMustSupportIssuedTokens()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_ISSUED_TOKENS));
            }
            if (trust10.isMustSupportServerChallenge()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_SERVER_CHALLENGE));
            }
            if (trust10.isRequireClientEntropy()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.REQUIRE_CLIENT_ENTROPY));
            }
            if (trust10.isRequireServerEntropy()) {
                policyAsserter.assertPolicy(new QName(namespace, SPConstants.REQUIRE_SERVER_ENTROPY));
            }
            if (trust10 instanceof Trust13) {
                Trust13 trust13 = (Trust13)trust10;
                if (trust13.isMustSupportInteractiveChallenge()) {
                    policyAsserter.assertPolicy(new QName(namespace, SPConstants.MUST_SUPPORT_INTERACTIVE_CHALLENGE));
                }
                if (trust13.isRequireAppliesTo()) {
                    policyAsserter.assertPolicy(new QName(namespace, SPConstants.REQUIRE_APPLIES_TO));
                }
                if (trust13.isRequireRequestSecurityTokenCollection()) {
                    policyAsserter.assertPolicy(new QName(namespace,
                                                          SPConstants.REQUIRE_REQUEST_SECURITY_TOKEN_COLLECTION));
                }
                if (trust13.isScopePolicy15()) {
                    policyAsserter.assertPolicy(new QName(namespace, SPConstants.SCOPE_POLICY_15));
                }
            }
        } else {
            policyAsserter.assertPolicy(abstractSecurityAssertion);
        }

        /*else if (abstractSecurityAssertion instanceof AsymmetricBinding) {
        } else if (abstractSecurityAssertion instanceof SymmetricBinding) {
        } else if (abstractSecurityAssertion instanceof TransportBinding) {
        } */

        return assertableList;
    }

    /**
     * tries to verify a SecurityEvent in realtime.
     *
     * @param securityEvent
     * @throws WSSPolicyException
     */
    private void verifyPolicy(SecurityEvent securityEvent) throws WSSPolicyException, XMLSecurityException {
        // We have to check the failed assertions for logging purposes firstly...
        if (!this.failedAssertionStateMap.isEmpty()) {
            Iterator<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMapIterator =
                this.failedAssertionStateMap.iterator();
            alternative:
                while (assertionStateMapIterator.hasNext()) {
                    Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> map =
                        assertionStateMapIterator.next();
                    // every list entry counts as an alternative...
                    Map<Assertion, List<Assertable>> assertionListMap = map.get(securityEvent.getSecurityEventType());
                    if (assertionListMap != null && !assertionListMap.isEmpty()) {
                        Iterator<Map.Entry<Assertion, List<Assertable>>> assertionStateIterator =
                            assertionListMap.entrySet().iterator();
                        while (assertionStateIterator.hasNext()) {
                            Map.Entry<Assertion, List<Assertable>> assertionStateEntry =
                                assertionStateIterator.next();
                            List<Assertable> assertionStates = assertionStateEntry.getValue();
                            Iterator<Assertable> assertableIterator = assertionStates.iterator();
                            while (assertableIterator.hasNext()) {
                                Assertable assertable = assertableIterator.next();
                                boolean asserted = assertable.assertEvent(securityEvent);
                                // ...so if one fails, continue with the next map entry and
                                // increment the notAssertedCount
                                if (!asserted) {
                                    continue alternative;
                                }
                            }
                        }
                    }
                }
        }

        String assertionMessage = null;
        //...and then check the remaining alternatives
        Iterator<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMapIterator =
            this.assertionStateMap.iterator();
        //every map entry counts as an alternative...
        alternative:
            while (assertionStateMapIterator.hasNext()) {
                Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> map =
                    assertionStateMapIterator.next();
                Map<Assertion, List<Assertable>> assertionListMap = map.get(securityEvent.getSecurityEventType());
                if (assertionListMap != null && !assertionListMap.isEmpty()) {
                    Iterator<Map.Entry<Assertion, List<Assertable>>> assertionStateIterator =
                        assertionListMap.entrySet().iterator();
                    while (assertionStateIterator.hasNext()) {
                        Map.Entry<Assertion, List<Assertable>> assertionStateEntry = assertionStateIterator.next();
                        List<Assertable> assertionStates = assertionStateEntry.getValue();
                        Iterator<Assertable> assertableIterator = assertionStates.iterator();
                        while (assertableIterator.hasNext()) {
                            Assertable assertable = assertableIterator.next();
                            boolean asserted = assertable.assertEvent(securityEvent);
                            //...so if one fails, continue with the next map entry and increment the notAssertedCount
                            if (!asserted) {
                                assertionMessage = assertable.getErrorMessage();
                                failedAssertionStateMap.add(map);
                                assertionStateMapIterator.remove();
                                continue alternative;
                            }
                        }
                    }
                }
            }
        //if the assertionStateMap is empty (the size of the list is equal to the alternatives)
        //then we could not satisfy any alternative
        if (assertionStateMap.isEmpty() && !(faultOccurred && noSecurityHeader && initiator)) {
            logFailedAssertions();
            throw new PolicyViolationException(assertionMessage);
        }
    }

    /**
     * verifies the whole policy to try to find a satisfied alternative
     *
     * @throws WSSPolicyException       throws when the policy is invalid
     * @throws PolicyViolationException thrown when no alternative could be satisfied
     */
    private void verifyPolicy() throws WSSPolicyException {
        String assertionMessage = null;
        Iterator<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMapIterator =
            this.assertionStateMap.iterator();
        alternative:
        while (assertionStateMapIterator.hasNext()) {
            Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> map =
                assertionStateMapIterator.next();
            Iterator<Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> iterator =
                map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> mapEntry =
                    iterator.next();
                Iterator<Map.Entry<Assertion, List<Assertable>>> assertionStateIterator =
                    mapEntry.getValue().entrySet().iterator();
                while (assertionStateIterator.hasNext()) {
                    Map.Entry<Assertion, List<Assertable>> assertionListEntry = assertionStateIterator.next();
                    List<Assertable> assertableList = assertionListEntry.getValue();
                    Iterator<Assertable> assertableIterator = assertableList.iterator();
                    while (assertableIterator.hasNext()) {
                        Assertable assertable = assertableIterator.next();
                        if (!assertable.isAsserted()) {
                            assertionMessage = assertable.getErrorMessage();
                            failedAssertionStateMap.add(map);
                            assertionStateMapIterator.remove();
                            continue alternative;
                        }
                    }
                }
            }
        }
        if (assertionStateMap.isEmpty() && !(faultOccurred && noSecurityHeader && initiator)) {
            logFailedAssertions();
            throw new WSSPolicyException(assertionMessage);
        }
    }

    /**
     * verifies the policy after the OperationSecurityEvent occurred. This allows to
     * stop further processing after the header is processed when the policy is not fulfilled.
     *
     * @throws WSSPolicyException       throws when the policy is invalid
     * @throws PolicyViolationException thrown when no alternative could be satisfied
     */
    private void verifyPolicyAfterOperationSecurityEvent() throws WSSPolicyException {
        String assertionMessage = null;
        Iterator<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMapIterator =
            this.assertionStateMap.iterator();
        alternative:
        while (assertionStateMapIterator.hasNext()) {
            Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> map =
                assertionStateMapIterator.next();
            Iterator<Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> iterator =
                map.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> mapEntry =
                    iterator.next();
                Iterator<Map.Entry<Assertion, List<Assertable>>> assertionStateIterator =
                    mapEntry.getValue().entrySet().iterator();
                while (assertionStateIterator.hasNext()) {
                    Map.Entry<Assertion, List<Assertable>> assertionListEntry = assertionStateIterator.next();
                    List<Assertable> assertableList = assertionListEntry.getValue();
                    Iterator<Assertable> assertableIterator = assertableList.iterator();
                    while (assertableIterator.hasNext()) {
                        Assertable assertable = assertableIterator.next();

                        boolean doAssert = false;
                        if (assertable instanceof TokenAssertionState) {
                            TokenAssertionState tokenAssertionState = (TokenAssertionState) assertable;
                            AbstractToken abstractToken = (AbstractToken) tokenAssertionState.getAssertion();
                            AbstractSecurityAssertion assertion = abstractToken.getParentAssertion();
                            //Other tokens may not be resolved yet fully therefore we skip it here
                            if (assertion instanceof SupportingTokens
                                || assertable instanceof HttpsTokenAssertionState
                                || assertable instanceof RelTokenAssertionState
                                || assertable instanceof SecurityContextTokenAssertionState
                                || assertable instanceof SpnegoContextTokenAssertionState
                                || assertable instanceof UsernameTokenAssertionState) {
                                doAssert = true;
                            }
                        } else if (assertable instanceof TokenProtectionAssertionState
                            || assertable instanceof SignatureConfirmationAssertionState
                            || assertable instanceof IncludeTimeStampAssertionState
                            || assertable instanceof RequiredPartsAssertionState
                            || assertable instanceof SignatureProtectionAssertionState) {
                            doAssert = true;
                        }

                        if ((doAssert || assertable.isHardFailure()) && !assertable.isAsserted()) {
                            assertionMessage = assertable.getErrorMessage();
                            failedAssertionStateMap.add(map);
                            assertionStateMapIterator.remove();
                            continue alternative;
                        }
                    }
                }
            }
        }
        if (assertionStateMap.isEmpty() && !(faultOccurred && noSecurityHeader && initiator)) {
            logFailedAssertions();
            throw new WSSPolicyException(assertionMessage);
        }
    }

    private void logFailedAssertions() {
        if (this.failedAssertionStateMap.isEmpty()) {
            return;
        }

        Iterator<Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> assertionStateMapIterator =
            this.failedAssertionStateMap.iterator();
        while (assertionStateMapIterator.hasNext()) {
            Map<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> map =
                assertionStateMapIterator.next();
            Set<Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> entrySet = map.entrySet();
            Iterator<Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>>> entryIterator =
                entrySet.iterator();
            while (entryIterator.hasNext()) {
                Map.Entry<SecurityEventConstants.Event, Map<Assertion, List<Assertable>>> eventCollectionEntry =
                    entryIterator.next();
                Map<Assertion, List<Assertable>> assertionListMap = eventCollectionEntry.getValue();
                Iterator<Map.Entry<Assertion, List<Assertable>>> assertionStateEntryIterator =
                    assertionListMap.entrySet().iterator();
                while (assertionStateEntryIterator.hasNext()) {
                    Map.Entry<Assertion, List<Assertable>> entry = assertionStateEntryIterator.next();
                    List<Assertable> assertionStates = entry.getValue();
                    Iterator<Assertable> assertableIterator = assertionStates.iterator();
                    while (assertableIterator.hasNext()) {
                        Assertable assertable = assertableIterator.next();
                        if (!assertable.isAsserted() && !assertable.isLogged()) {
                            LOG.error(entry.getKey().getName() + " not satisfied: " + assertable.getErrorMessage());
                            assertable.setLogged(true);
                        }
                    }
                }
            }
        }
    }

    //multiple threads can call this method concurrently -> synchronize access
    @Override
    public synchronized void registerSecurityEvent(SecurityEvent securityEvent) throws WSSecurityException {

        if (!noSecurityHeader && securityEvent instanceof NoSecuritySecurityEvent) {
            noSecurityHeader = true;
        }

        if (operationSecurityEventOccured) {
            try {
                verifyPolicy(securityEvent);
            } catch (WSSPolicyException | XMLSecurityException e) {
                throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
            }
        }

        if (WSSecurityEventConstants.OPERATION.equals(securityEvent.getSecurityEventType())) {
            operationSecurityEventOccured = true;
            final OperationSecurityEvent operationSecurityEvent = (OperationSecurityEvent) securityEvent;
            if (!faultOccurred && (SOAP11_FAULT.equals(operationSecurityEvent.getOperation())
                || SOAP12_FAULT.equals(operationSecurityEvent.getOperation()))) {
                faultOccurred = true;
            }

            if (effectivePolicy == null) {
                effectivePolicy = findPolicyBySOAPOperationName(operationPolicies,
                                                                operationSecurityEvent.getOperation());
                if (effectivePolicy == null) {
                    //no policy to the operation given
                    effectivePolicy = new OperationPolicy(new QName(null, "NoPolicyFoundForOperation"));
                    effectivePolicy.setPolicy(new Policy());
                }
                try {
                    buildAssertionStateMap(effectivePolicy.getPolicy(), assertionStateMap);
                } catch (WSSPolicyException e) {
                    throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
                }
            }
            try {
                Iterator<SecurityEvent> securityEventIterator = securityEventQueue.descendingIterator();
                while (securityEventIterator.hasNext()) {
                    SecurityEvent prevSecurityEvent = securityEventIterator.next();
                    verifyPolicy(prevSecurityEvent);
                }

                verifyPolicy(securityEvent);

                verifyPolicyAfterOperationSecurityEvent();
            } catch (WSSPolicyException | XMLSecurityException e) {
                throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
            }
            securityEventQueue.clear();

            return;
        } else {
            securityEventQueue.push(securityEvent);
        }
    }

    /**
     * the final Policy validation to find a satisfied alternative
     *
     * @throws PolicyViolationException if no alternative could be satisfied
     */
    public void doFinal() throws WSSPolicyException {
        verifyPolicy();
    }

}
