PolicyWrapper.java

/*******************************************************************************
 * Copyright 2018 IIT-CNR
 *
 * 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 it.cnr.iit.xacml.wrappers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;

import it.cnr.iit.ucs.exceptions.PolicyException;
import it.cnr.iit.ucs.message.tryaccess.TryAccessMessage;
import it.cnr.iit.ucs.pap.PAPInterface;
import it.cnr.iit.utility.JAXBUtility;
import it.cnr.iit.utility.errorhandling.Reject;
import it.cnr.iit.xacml.Attribute;
import it.cnr.iit.xacml.Category;
import it.cnr.iit.xacml.DataType;
import it.cnr.iit.xacml.PolicyTags;

import oasis.names.tc.xacml.core.schema.wd_17.ApplyType;
import oasis.names.tc.xacml.core.schema.wd_17.AttributeDesignatorType;
import oasis.names.tc.xacml.core.schema.wd_17.AttributeValueType;
import oasis.names.tc.xacml.core.schema.wd_17.ConditionType;
import oasis.names.tc.xacml.core.schema.wd_17.EffectType;
import oasis.names.tc.xacml.core.schema.wd_17.PolicyType;
import oasis.names.tc.xacml.core.schema.wd_17.RuleType;

/**
 * This is a wrapper for the policy class.
 *
 * @author Antonio La Marra, Alessandro Rosetti
 *
 */
public class PolicyWrapper implements PolicyWrapperInterface {

    private static final Logger log = Logger.getLogger( PolicyWrapper.class.getName() );

    private static final int MAX_CONDITION_LENGTH = 20;

    private PolicyType policyType;
    private String policy;

    private PolicyWrapper() {}

    public static PolicyWrapper build( String policy ) throws PolicyException {
        PolicyWrapper policyWrapper = new PolicyWrapper();
        try {
            PolicyType policyType = unmarshalPolicyType( policy );
            policyWrapper.setPolicyType( policyType );
        } catch( JAXBException e ) {
            throw new PolicyException( "Error unmarshalling policy : {0}" + e.getMessage() );
        }
        policyWrapper.setPolicy( policy );
        return policyWrapper;
    }

    public static PolicyWrapper build( PolicyType policyType ) throws PolicyException {
        PolicyWrapper policyWrapper = new PolicyWrapper();
        try {
            String policy = marshalPolicyType( policyType );
            policyWrapper.setPolicy( policy );
        } catch( JAXBException e ) {
            throw new PolicyException( "Error marshalling policy : {0}" + e.getMessage() );
        }
        policyWrapper.setPolicyType( policyType );
        return policyWrapper;
    }

    public static PolicyWrapper build( PAPInterface pap, TryAccessMessage message ) throws PolicyException {
        String policy = message.getPolicy();
        if( (policy == null || policy.isEmpty()) && message.getPolicyId() != null && !message.getPolicyId().isEmpty() ) {
            policy = pap.retrievePolicy( message.getPolicyId() );
        }
        return PolicyWrapper.build( policy );
    }

    @Override
    public List<Attribute> getAttributesForCondition( String conditionName ) {
        Reject.ifBlank( conditionName );
        Reject.ifTrue( conditionName.length() > MAX_CONDITION_LENGTH );
        for( RuleType ruleType : policyType.getRuleTypeList() ) {
            List<ConditionType> conditionTypeList = ruleType.getCondition();
            if( conditionTypeList != null ) {
                for( ConditionType conditionType : conditionTypeList ) {
                    List<Attribute> attributeList = getAttributesFromCondition( conditionType, conditionName );
                    if( !attributeList.isEmpty() ) {
                        return attributeList;
                    }
                }
            }
        }
        log.log( Level.WARNING, "Condition not found : {0}", conditionName );
        return new ArrayList<>();
    }

    private List<Attribute> getAttributesFromCondition( ConditionType conditionType, String conditionName ) {
        if( conditionType.getDecisionTime() == null ) {
            if( conditionName.equals( PolicyTags.CONDITION_PRE ) ) {
                return getAttributesFromCondition( conditionType );
            }
        } else if( conditionType.getDecisionTime().equals( conditionName ) ) {
            return getAttributesFromCondition( conditionType );
        }
        return new ArrayList<>();
    }


    /**
     * Given a root node, build a number of lists equal to the number of attributes found within the tree
     * and save them in the elementList.
     * Each list should contain exactly one element whose value is of type AttributeDesignatorType, and one
     * or more elements whose value is of type AttributeValueType.
     * When this list (auxList) is fully populated, it is cloned and added to the elementList. Then, it is
     * reset to be used in the next recursive iterations.
     * @param node the root node we want start the iterations from
     * @param elementList list containing lists. Each list is related to one attribute and should contain exactly
     *                    one element whose value is of type AttributeDesignatorType, and one or more elements
     *                    whose value is of type AttributeValueType.
     * @param auxList a list that temporarily contains the information related to one attribute. When fully
     *                populated, this list is cloned and added to the elementList. Then, it is reset to be used
     *                in the next recursive iterations.
     */
    private void recursiveGetChildren(JAXBElement<?> node, List<ArrayList<JAXBElement<?>>> elementList, ArrayList<JAXBElement<?>> auxList) {
        Object objValue = node.getValue();
        if( objValue instanceof ApplyType ) {
            ApplyType applyType = (ApplyType) objValue;
            ArrayList<JAXBElement<?>> children = (ArrayList<JAXBElement<?>>) applyType.getExpression();
            boolean hasAnAttributeValueTypeChild = false;
            for (JAXBElement<?> jaxbElement : children) {
                Object child = jaxbElement.getValue();
                if (child instanceof AttributeValueType) {
                    // At least one direct child of this node is of type AttributeValueType
                    hasAnAttributeValueTypeChild = true;
                }
            }
            for (JAXBElement<?> jaxbElement : children) {
                recursiveGetChildren(jaxbElement, elementList, auxList);
            }
            if (hasAnAttributeValueTypeChild) {
                // if we get here it means that the node is of type ApplyType, it has at least
                // one child of type AttributeValueType, and the recursion of this node is terminated,
                // i.e., all its child nodes have been visited and the attributeList has been fully
                // populated.
                elementList.add((ArrayList<JAXBElement<?>>) auxList.clone());
                auxList.clear();
            }
        } else if (objValue instanceof AttributeValueType) {
            auxList.add(node);
        } else if (objValue instanceof AttributeDesignatorType) {
            auxList.add(node);
        }
    }


    /**
     * Function that effectively extracts the attributes from the condition.
     * The attribute object we have built up, embeds two different complex types
     * in the xsd: one is the AttributeDesignator, the other is the attribute
     * value.
     *
     * @param conditionType the condition we are analysing
     * @return the list of attributes contained in this condition.
     */
    private List<Attribute> getAttributesFromCondition( ConditionType conditionType ) {
        List<ArrayList<JAXBElement<?>>> elementList = new ArrayList<>();

        // populate the elementList as a list of lists. Each list contains
        // one or more elements whose value is of type AttributeValueType
        // and only one element whose value is of type AttributeDesignatorType
        recursiveGetChildren(conditionType.getExpression(), elementList, new ArrayList<>());

        ArrayList<Attribute> attributesList = new ArrayList<>();

        for (ArrayList<JAXBElement<?>> attributeValuesAndDesignatorList : elementList) {
            List<List<Object>> dataTypeAndValuesList = new ArrayList<>();

            Attribute attribute = new Attribute();

            // get the attribute values from the elements whose value is of type AttributeValueType
            // and store them in the dataTypeAndValuesList
            for (JAXBElement<?> jaxbElement : attributeValuesAndDesignatorList) {
                if (jaxbElement.getValue() instanceof AttributeValueType) {
                    AttributeValueType attributeValueType = (AttributeValueType) jaxbElement.getValue();
                    for (Object obj : attributeValueType.getContent()) {
                        dataTypeAndValuesList.add(Arrays.asList(attributeValueType.getDataType(), obj));
                    }
                }
            }

            // get the other info from the element whose value is of type AttributeDesignatorType,
            // build the attribute of type Attribute, and add it to the list that will be returned
            for (JAXBElement<?> jaxbElement : attributeValuesAndDesignatorList) {
                if (jaxbElement.getValue() instanceof AttributeDesignatorType) {
                    AttributeDesignatorType attrDesignatorType = (AttributeDesignatorType) jaxbElement.getValue();

                    attribute.setAttributeId(attrDesignatorType.getAttributeId());
                    attribute.setCategory(Category.toCATEGORY(attrDesignatorType.getCategory()));
                    attribute.setDataType(DataType.toDATATYPE(attrDesignatorType.getDataType()));

                    for (List<Object> o : dataTypeAndValuesList) {
                        // the first element of the list is the datatype, while the second is the attribute value
                        attribute.setAttributeValues(o.get(0).toString(), o.get(1).toString());
                    }
                    attributesList.add(attribute);
                }
            }
        }
        return attributesList;
    }


    @Override
    public String retrieveObligations() {
        log.log( Level.WARNING, "retrieveObligations is unimplemented" );
        return null;
    }

    @Override
    public String getRuleCombiningAlgorithmId() {
        return policyType.getRuleCombiningAlgId();
    }

    /**
     * In UXACML we may have 3 types of conditions: pre, ongoing and post.
     * This function retrieves the policy with the required condition.
     *
     * @param conditionName
     *          the required condition
     * @return a copy of the policyType containing only the required condition
     * @throws PolicyException
     */
    @Override
    public PolicyWrapper getPolicyForCondition( String conditionName ) throws PolicyException {
        PolicyType clonedPolicyType = clonePolicyTypeWithoutRules();
        List<Object> objectList = policyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition();
        List<Object> clonedObjectList = clonedPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition();

        for( Object obj : objectList ) {
            RuleType ruleType = (RuleType) obj;
            /* If this list of objects contains a ruleType with a condition list it must be analysed.
              In any other case the object will be copied inside cloned list. */
            if( !( obj instanceof RuleType ) ||
                    ( ruleType.getCondition() == null || ruleType.getCondition().isEmpty() ) ) {
                clonedObjectList.add( obj );
                continue;
            }
            analyseRuleType( clonedObjectList, ruleType, conditionName );
        }

        return PolicyWrapper.build( clonedPolicyType );
    }

    private void analyseRuleType( List<Object> objectList, RuleType ruleType, String conditionName ) {
        for( ConditionType conditionType : ruleType.getCondition() ) {
            RuleType clonedRuleType;
            if( conditionType.getDecisionTime() == null ) {
                if( conditionName.equals( PolicyTags.CONDITION_PRE ) ) {
                    clonedRuleType = cloneRuleType( ruleType, conditionType );
                } else {
                    clonedRuleType = getDefaultRuleType( "def-permit", EffectType.PERMIT );
                    clonedRuleType.setObligationExpressions( ruleType.getObligationExpressions() );
                }
                objectList.add( clonedRuleType );
            } else if( conditionType.getDecisionTime().equals( conditionName ) ) {
                clonedRuleType = cloneRuleType( ruleType, conditionType );
                objectList.add( clonedRuleType );
                break;
            }
        }
    }

    public String getPolicy() {
        return policy;
    }

    public void setPolicy( String policy ) {
        this.policy = policy;
    }

    public PolicyType getPolicyType() {
        return policyType;
    }

    public void setPolicyType( PolicyType policyType ) {
        this.policyType = policyType;
    }

    private RuleType getDefaultRuleType( String id, EffectType effectType ) {
        RuleType ruleType = new RuleType();
        ruleType.setEffect( effectType );
        ruleType.setRuleId( id );
        return ruleType;
    }

    /**
     * Performs a copy of the ruleType object.
     *
     * @param ruleType
     *          the ruleType object we want to copy
     * @param conditionType
     *          the condition to be put inside the new ruleType object
     * @return the ruleType object built in this way
     */
    private RuleType cloneRuleType( RuleType ruleType, ConditionType conditionType ) {
        RuleType newRuleType = new RuleType();
        newRuleType.getCondition().add( conditionType );
        newRuleType.setAdviceExpressions( ruleType.getAdviceExpressions() );
        newRuleType.setDescription( ruleType.getDescription() );
        newRuleType.setObligationExpressions( ruleType.getObligationExpressions() );
        newRuleType.setEffect( ruleType.getEffect() );
        newRuleType.setRuleId( ruleType.getRuleId() );
        newRuleType.setTarget( ruleType.getTarget() );

        return newRuleType;
    }

    /**
     * Performs a partial copy of the policyType object.
     *
     * @return the PolicyType object that is the copy of the one stored in this
     *         object
     */
    private PolicyType clonePolicyTypeWithoutRules() {
        PolicyType newPolicyType = new PolicyType();
        newPolicyType.setDescription( policyType.getDescription() );
        newPolicyType.setPolicyId( policyType.getPolicyId() );
        newPolicyType.setPolicyIssuer( policyType.getPolicyIssuer() );
        newPolicyType.setAdviceExpressions( policyType.getAdviceExpressions() );
        newPolicyType.setMaxDelegationDepth( policyType.getMaxDelegationDepth() );
        newPolicyType.setPolicyDefaults( policyType.getPolicyDefaults() );
        newPolicyType.setRuleCombiningAlgId( policyType.getRuleCombiningAlgId() );
        newPolicyType.setTarget( policyType.getTarget() );
        newPolicyType.setVersion( policyType.getVersion() );
        newPolicyType.setObligationExpressions( policyType.getObligationExpressions() );
        return newPolicyType;
    }

    private static PolicyType unmarshalPolicyType( String policy ) throws JAXBException {
        return JAXBUtility.unmarshalToObject( PolicyType.class, policy );
    }

    private static String marshalPolicyType( PolicyType policy ) throws JAXBException {
        return JAXBUtility.marshalToString( PolicyType.class, policy, PolicyTags.POLICY, JAXBUtility.SCHEMA );
    }

}