org.ow2.authzforce.pap.dao.flatfile.FlatFileDAORefPolicyProviderModule.java Source code

Java tutorial

Introduction

Here is the source code for org.ow2.authzforce.pap.dao.flatfile.FlatFileDAORefPolicyProviderModule.java

Source

/**
 * Copyright (C) 2012-2016 Thales Services SAS.
 *
 * This file is part of AuthZForce CE.
 *
 * AuthZForce CE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * AuthZForce CE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with AuthZForce CE.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.ow2.authzforce.pap.dao.flatfile;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.bind.JAXBException;

import org.ow2.authzforce.core.pdp.api.EnvironmentProperties;
import org.ow2.authzforce.core.pdp.api.EvaluationContext;
import org.ow2.authzforce.core.pdp.api.IndeterminateEvaluationException;
import org.ow2.authzforce.core.pdp.api.JaxbXACMLUtils.XACMLParserFactory;
import org.ow2.authzforce.core.pdp.api.StatusHelper;
import org.ow2.authzforce.core.pdp.api.XMLUtils.NamespaceFilteringParser;
import org.ow2.authzforce.core.pdp.api.combining.CombiningAlgRegistry;
import org.ow2.authzforce.core.pdp.api.expression.ExpressionFactory;
import org.ow2.authzforce.core.pdp.api.policy.PolicyVersion;
import org.ow2.authzforce.core.pdp.api.policy.RefPolicyProviderModule;
import org.ow2.authzforce.core.pdp.api.policy.StaticRefPolicyProviderModule;
import org.ow2.authzforce.core.pdp.api.policy.StaticTopLevelPolicyElementEvaluator;
import org.ow2.authzforce.core.pdp.api.policy.TopLevelPolicyElementEvaluator;
import org.ow2.authzforce.core.pdp.api.policy.TopLevelPolicyElementType;
import org.ow2.authzforce.core.pdp.api.policy.VersionPatterns;
import org.ow2.authzforce.core.pdp.impl.policy.PolicyEvaluators;
import org.ow2.authzforce.core.pdp.impl.policy.PolicyMap;
import org.ow2.authzforce.pap.dao.flatfile.FlatFileDAOUtils.SuffixMatchingDirectoryStreamFilter;
import org.ow2.authzforce.pap.dao.flatfile.xmlns.StaticFlatFileDAORefPolicyProvider;
import org.springframework.util.ResourceUtils;

import com.koloboke.collect.map.hash.HashObjObjMaps;

import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySet;

/**
 * Static Ref Policy Provider for the File-based PAP DAO. This provider expects
 * to find a XACML PolicySet file at
 * PARENT_DIRECTORY/base64url(${PolicySetId})/${Version}SUFFIX. PolicySetId and
 * Version are the respective XACML attributes of the PolicySet.
 * PARENT_DIRECTORY is the parent directory where all policies are located, one
 * directory per each policy (one sub-file per policy version), as defined by
 * the 'policyLocation' attribute.
 * <p>
 * 'base64url' function refers to Base64url encoding specified by RFC 4648,
 * without padding.
 */
public final class FlatFileDAORefPolicyProviderModule implements StaticRefPolicyProviderModule {
    private static final IllegalArgumentException NULL_POLICY_LOCATION_PATTERN_ARGUMENT_EXCEPTION = new IllegalArgumentException(
            "policyLocationPattern argument undefined");

    private static final IllegalArgumentException NULL_XML_CONF_ARGUMENT_EXCEPTION = new IllegalArgumentException(
            "XML/JAXB configuration argument undefined");

    private static final IllegalArgumentException UNSUPPORTED_POLICY_REFERENCE_EXCEPTION = new IllegalArgumentException(
            "PolicyIdReferences not supported");

    /**
     * Validate provider config and returns policy parent directory and policy
     * (version-specific) filename suffix
     * 
     * @param policyLocationPattern
     *            policy location pattern, expected to be
     *            PARENT_DIRECTORY/*SUFFIX, where PARENT_DIRECTORY is a valid
     *            directory path where the policies should be located.
     * @return entry where the key is the parent directory to all policies, and
     *         the value is the policy filename suffix for each policy version
     * @throws IllegalArgumentException
     *             if the policyLocationPattern is invalid
     */
    public static Entry<Path, String> validateConf(final String policyLocationPattern)
            throws IllegalArgumentException {
        if (policyLocationPattern == null) {
            throw NULL_POLICY_LOCATION_PATTERN_ARGUMENT_EXCEPTION;
        }

        final int index = policyLocationPattern.indexOf("/*");
        if (index == -1) {
            throw new IllegalArgumentException("Invalid policyLocationPattern in refPolicyProvider configuration: "
                    + policyLocationPattern + ": '/*' not found");
        }

        final String prefix = policyLocationPattern.substring(0, index);
        final Path policyParentDirectory;
        try {
            policyParentDirectory = ResourceUtils.getFile(prefix).toPath();
        } catch (final FileNotFoundException e) {
            throw new IllegalArgumentException(
                    "Invalid policy directory path in refPolicyProvider/policyLocationPattern (prefix before '/*'): "
                            + policyLocationPattern,
                    e);
        }

        final String suffix = policyLocationPattern.substring(index + 2);
        return new SimpleImmutableEntry<>(policyParentDirectory, suffix);
    }

    /**
     * Module factory
     *
     */
    public static final class Factory extends RefPolicyProviderModule.Factory<StaticFlatFileDAORefPolicyProvider> {
        private static final IllegalArgumentException ILLEGAL_COMBINING_ALG_REGISTRY_ARGUMENT_EXCEPTION = new IllegalArgumentException(
                "Undefined CombiningAlgorithm registry");
        private static final IllegalArgumentException ILLEGAL_EXPRESSION_FACTORY_ARGUMENT_EXCEPTION = new IllegalArgumentException(
                "Undefined Expression factory");
        private static final IllegalArgumentException ILLEGAL_XACML_PARSER_FACTORY_ARGUMENT_EXCEPTION = new IllegalArgumentException(
                "Undefined XACML parser factory");

        @Override
        public RefPolicyProviderModule getInstance(final StaticFlatFileDAORefPolicyProvider conf,
                final XACMLParserFactory xacmlParserFactory, final int maxPolicySetRefDepth,
                final ExpressionFactory expressionFactory, final CombiningAlgRegistry combiningAlgRegistry,
                final EnvironmentProperties environmentProperties) throws IllegalArgumentException {
            if (conf == null) {
                throw NULL_XML_CONF_ARGUMENT_EXCEPTION;
            }

            if (xacmlParserFactory == null) {
                throw ILLEGAL_XACML_PARSER_FACTORY_ARGUMENT_EXCEPTION;
            }

            if (expressionFactory == null) {
                throw ILLEGAL_EXPRESSION_FACTORY_ARGUMENT_EXCEPTION;
            }

            if (combiningAlgRegistry == null) {
                throw ILLEGAL_COMBINING_ALG_REGISTRY_ARGUMENT_EXCEPTION;
            }

            final String policyLocationPattern = environmentProperties
                    .replacePlaceholders(conf.getPolicyLocationPattern());
            final Entry<Path, String> result = validateConf(policyLocationPattern);
            return new FlatFileDAORefPolicyProviderModule(result.getKey(), result.getValue(), xacmlParserFactory,
                    expressionFactory, combiningAlgRegistry, maxPolicySetRefDepth);
        }

        @Override
        public Class<StaticFlatFileDAORefPolicyProvider> getJaxbClass() {
            return StaticFlatFileDAORefPolicyProvider.class;
        }

    }

    /*
     * Lazy initializing policy evaluator, i.e. only when the policy is actually
     * requested; because this job is expensive.
     */
    private static final class PolicyEvaluatorSupplier {
        private final Path policyFilepath;
        private transient StaticTopLevelPolicyElementEvaluator policyEvaluator = null;

        private PolicyEvaluatorSupplier(final Path policyFilepath) {
            assert policyFilepath != null && Files.isRegularFile(policyFilepath, LinkOption.NOFOLLOW_LINKS)
                    && Files.isReadable(policyFilepath);
            this.policyFilepath = policyFilepath;
        }

        private StaticTopLevelPolicyElementEvaluator get(
                final FlatFileDAORefPolicyProviderModule refPolicyProviderModule,
                final Deque<String> policySetRefChain) throws IndeterminateEvaluationException {
            assert policySetRefChain != null;
            /*
             * Prevent simulatenous attemps to initialize the policy evaluator.
             * Must be done by a single thread once and for all
             */
            synchronized (policyFilepath) {
                if (policyEvaluator == null) {
                    if (!Files.isRegularFile(policyFilepath, LinkOption.NOFOLLOW_LINKS)) {
                        throw new IndeterminateEvaluationException(
                                "Unable to find PolicySet file: " + policyFilepath,
                                StatusHelper.STATUS_PROCESSING_ERROR);
                    }

                    final NamespaceFilteringParser xacmlParser;
                    final PolicySet jaxbPolicySet;
                    try {
                        xacmlParser = refPolicyProviderModule.xacmlParserFactory.getInstance();
                        jaxbPolicySet = FlatFileDAOUtils.loadPolicy(policyFilepath, xacmlParser);
                    } catch (final IllegalArgumentException e) {
                        throw new IndeterminateEvaluationException("Invalid PolicySet in file: " + policyFilepath,
                                StatusHelper.STATUS_PROCESSING_ERROR, e);
                    } catch (final JAXBException e1) {
                        throw new IndeterminateEvaluationException(
                                "Error loading PolicySet from file: " + policyFilepath,
                                StatusHelper.STATUS_PROCESSING_ERROR, e1);
                    }
                    try {
                        policyEvaluator = PolicyEvaluators.getInstanceStatic(jaxbPolicySet, null,
                                xacmlParser.getNamespacePrefixUriMap(), refPolicyProviderModule.expressionFactory,
                                refPolicyProviderModule.combiningAlgRegistry, refPolicyProviderModule,
                                policySetRefChain);
                    } catch (final IllegalArgumentException e) {
                        throw new IndeterminateEvaluationException("Invalid PolicySet in file: " + policyFilepath,
                                StatusHelper.STATUS_PROCESSING_ERROR, e);
                    }
                }
            }

            return policyEvaluator;
        }
    }

    private final XACMLParserFactory xacmlParserFactory;
    private final ExpressionFactory expressionFactory;
    private final CombiningAlgRegistry combiningAlgRegistry;
    private final int maxPolicyRefDepth;
    // policyId -> cache(PolicySets by policy version)
    private final PolicyMap<PolicyEvaluatorSupplier> policyCache;

    private FlatFileDAORefPolicyProviderModule(final Path policyParentDirectory, final String suffix,
            final XACMLParserFactory xacmlParserFactory, final ExpressionFactory expressionFactory,
            final CombiningAlgRegistry combiningAlgRegistry, final int maxPolicySetRefDepth)
            throws IllegalArgumentException {
        assert policyParentDirectory != null;
        assert xacmlParserFactory != null;
        assert expressionFactory != null;
        assert combiningAlgRegistry != null;

        FlatFileDAOUtils.checkFile("RefPolicyProvider's policy directory", policyParentDirectory, true, false);
        final Map<String, Map<PolicyVersion, PolicyEvaluatorSupplier>> updatablePolicyMap = HashObjObjMaps
                .newUpdatableMap();
        // filter matching specifc file suffix for policy files
        final Filter<? super Path> policyFilenameSuffixMatchingDirStreamFilter = new SuffixMatchingDirectoryStreamFilter(
                suffix);
        try (final DirectoryStream<Path> policyParentDirStream = Files.newDirectoryStream(policyParentDirectory,
                FlatFileDAOUtils.SUB_DIRECTORY_STREAM_FILTER)) {
            // Browse directories of policies, one for each policy ID
            for (final Path policyVersionsDir : policyParentDirStream) {
                /*
                 * FindBugs considers there is a potential NullPointerException
                 * here since getFileName() may be null
                 */
                final Path lastPathSegment = policyVersionsDir.getFileName();
                if (lastPathSegment == null) {
                    throw new IllegalArgumentException(
                            "Invalid policy directory: no filename (root of filesystem?): " + policyVersionsDir);
                }

                final String policyDirName = lastPathSegment.toString();
                final String policyId;
                try {
                    policyId = FlatFileDAOUtils.base64UrlDecode(policyDirName);
                } catch (final IllegalArgumentException e) {
                    throw new IllegalArgumentException(
                            "Invalid policy directory: bad filename (not Base64URL-encoded): " + policyDirName, e);
                }

                final Map<PolicyVersion, PolicyEvaluatorSupplier> policySetSuppliersByVersion = HashObjObjMaps
                        .newUpdatableMap();
                // Browse policy versions, one policy file for each version of
                // the current policy
                try (final DirectoryStream<Path> policyVersionsDirStream = Files
                        .newDirectoryStream(policyVersionsDir, policyFilenameSuffixMatchingDirStreamFilter)) {
                    for (final Path policyVersionFile : policyVersionsDirStream) {
                        /*
                         * The PolicyEvaluator supplier (from file) allows to
                         * instantiate the Evaluator only if needed, because the
                         * instantiation of a PolicyEvaluator from a file is
                         * expensive.
                         */
                        policySetSuppliersByVersion.put(
                                new PolicyVersion(FlatFileDAOUtils.getPrefix(policyVersionFile, suffix.length())),
                                new PolicyEvaluatorSupplier(policyVersionFile));
                    }
                } catch (final IOException e) {
                    throw new IllegalArgumentException("Error listing files of each version of policy '" + policyId
                            + "' in directory: " + policyParentDirectory, e);
                }

                updatablePolicyMap.put(policyId, policySetSuppliersByVersion);
            }
        } catch (final IOException e) {
            throw new IllegalArgumentException(
                    "Error listing files in policies parent directory '" + policyParentDirectory, e);
        }

        this.policyCache = new PolicyMap<>(updatablePolicyMap);
        this.xacmlParserFactory = xacmlParserFactory;
        this.expressionFactory = expressionFactory;
        this.combiningAlgRegistry = combiningAlgRegistry;
        this.maxPolicyRefDepth = maxPolicySetRefDepth;
    }

    @Override
    public StaticTopLevelPolicyElementEvaluator get(final TopLevelPolicyElementType policyType, final String id,
            final VersionPatterns versionPatterns, final Deque<String> ancestorPolicyRefChain)
            throws IndeterminateEvaluationException {
        if (policyType == TopLevelPolicyElementType.POLICY) {
            throw UNSUPPORTED_POLICY_REFERENCE_EXCEPTION;
        }

        final Deque<String> newPolicyRefChain = Utils.appendAndCheckPolicyRefChain(ancestorPolicyRefChain,
                Collections.singletonList(id), maxPolicyRefDepth);

        // Request for PolicySetEvaluator (from PolicySetIdReference)
        final Entry<PolicyVersion, PolicyEvaluatorSupplier> policyEntry = policyCache.get(id, versionPatterns);
        if (policyEntry == null) {
            return null;
        }

        final int refChainLenBefore = newPolicyRefChain.size();
        final StaticTopLevelPolicyElementEvaluator policyEvaluator;
        try {
            policyEvaluator = policyEntry.getValue().get(this, newPolicyRefChain);
        } catch (final IndeterminateEvaluationException e) {
            // throw back an high-level exception message for easier
            // troubleshooting (no file path)
            final PolicyVersion version = policyEntry.getKey();
            throw new IndeterminateEvaluationException(
                    "Matched PolicySet '" + id + "' (version " + version
                            + ") is invalid or its content is unavailable",
                    StatusHelper.STATUS_PROCESSING_ERROR, e);
        }

        final List<String> resultPolicyLongestRefChain = policyEvaluator.getExtraPolicyMetadata()
                .getLongestPolicyRefChain();
        /*
         * If there is a longest ref chain in result policy, but
         * newPolicyRefChain was not updated with it (length unchanged, i.e.
         * same as before the get(...)), it means the policy was already parsed
         * before this retrieval (longest ref chain already computed).
         * Therefore, we need to take into account the longest policy ref chain
         * already computed in the result policy with the current policy ref
         * chain up to this result policy, i.e. newPolicyRefChain; and check the
         * total chain length.
         */
        if (resultPolicyLongestRefChain != null && !resultPolicyLongestRefChain.isEmpty()
                && newPolicyRefChain.size() == refChainLenBefore) {
            // newPolicyRefChain was not updated, so we assumed the result
            // policy was already parsed, and longest ref chain already computed
            // To get the new longest ref chain, we need to combine the two
            Utils.appendAndCheckPolicyRefChain(newPolicyRefChain, resultPolicyLongestRefChain, maxPolicyRefDepth);
        }

        return policyEvaluator;
    }

    @Override
    public TopLevelPolicyElementEvaluator get(final TopLevelPolicyElementType policyType, final String policyId,
            final VersionPatterns policyVersionConstraints, final Deque<String> policySetRefChain,
            final EvaluationContext evaluationCtx)
            throws IllegalArgumentException, IndeterminateEvaluationException {
        return get(policyType, policyId, policyVersionConstraints, policySetRefChain);
    }

    @Override
    public void close() throws IOException {
        /*
         * The policyCache has been made immutable so we cannot call the clear()
         * method
         */
        // this.policyCache.clear();
    }

}